示例#1
0
 def check_string(self, ctx, message, s):
     prefix = message_repr(message, template='{}:')
     fmt = None
     try:
         fmt = backend.FormatString(s)
     except backend.ArgumentTypeMismatch as exc:
         [s, key, types] = exc.args  # pylint: disable=unbalanced-tuple-unpacking
         self.tag(
             'python-format-string-error',
             prefix,
             tags.safestr(exc.message),
             tags.safestr(key),
             tags.safestr(', '.join(sorted(x for x in types))),
         )
     except backend.Error as exc:
         self.tag('python-format-string-error', prefix,
                  tags.safestr(exc.message), *exc.args[:1])
     if fmt is None:
         return
     for warn in fmt.warnings:
         try:
             raise warn
         except backend.RedundantFlag as exc:
             if len(exc.args) == 2:
                 [s, *args] = exc.args
             else:
                 [s, a1, a2] = exc.args
                 if a1 == a2:
                     args = ['duplicate', a1]
                 else:
                     args = [a1, tags.safe_format('overridden by {}', a2)]
             args += ['in', s]
             self.tag('python-format-string-redundant-flag', prefix, *args)
         except backend.RedundantPrecision as exc:
             [s, a] = exc.args
             self.tag('python-format-string-redundant-precision', prefix, a,
                      'in', s)
         except backend.RedundantLength as exc:
             [s, a] = exc.args
             self.tag('python-format-string-redundant-length', prefix, a,
                      'in', s)
         except backend.ObsoleteConversion as exc:
             [s, c1, c2] = exc.args
             args = [c1, '=>', c2]
             if s != c1:
                 args += ['in', s]
             self.tag('python-format-string-obsolete-conversion', prefix,
                      *args)
     if ctx.is_template:
         if len(fmt.seq_conversions) > 1:
             self.tag('python-format-string-multiple-unnamed-arguments',
                      message_repr(message))
         elif len(fmt.seq_conversions) == 1:
             arg_for_plural = (message.msgid_plural is not None
                               and fmt.seq_conversions[0].type == 'int')
             if arg_for_plural:
                 self.tag('python-format-string-unnamed-plural-argument',
                          message_repr(message))
     return fmt
示例#2
0
 def check_msgids(self, message, msgid_fmts):
     if msgid_fmts.get(0) is not None:
         try:
             [[arg]] = msgid_fmts[0].arguments
         except ValueError:
             pass
         else:
             if arg.type == 'int *':
                 self.tag('qt-plural-format-mistaken-for-c-format',
                          message_repr(message))
示例#3
0
 def check_string(self, ctx, message, s):
     prefix = message_repr(message, template='{}:')
     fmt = None
     try:
         fmt = backend.FormatString(s)
     except backend.MissingArgument as exc:
         self.tag(
             'c-format-string-error',
             prefix,
             tags.safestr(exc.message),
             tags.safestr('{1}$'.format(*exc.args)),
         )
     except backend.ArgumentTypeMismatch as exc:
         self.tag(
             'c-format-string-error',
             prefix,
             tags.safestr(exc.message),
             tags.safestr('{1}$'.format(*exc.args)),
             tags.safestr(', '.join(sorted(x for x in exc.args[2]))),
         )
     except backend.FlagError as exc:
         [conv, flag] = exc.args  # pylint: disable=unbalanced-tuple-unpacking
         self.tag('c-format-string-error', prefix,
                  tags.safestr(exc.message), flag, tags.safestr('in'), conv)
     except backend.Error as exc:
         self.tag('c-format-string-error', prefix,
                  tags.safestr(exc.message), *exc.args[:1])
     if fmt is None:
         return
     for warn in fmt.warnings:
         try:
             raise warn
         except backend.RedundantFlag as exc:
             if len(exc.args) == 2:
                 [s, *args] = exc.args
             else:
                 [s, a1, a2] = exc.args
                 if a1 == a2:
                     args = ['duplicate', a1]
                 else:
                     args = [a1, tags.safe_format('overridden by {}', a2)]
             args += ['in', s]
             self.tag('c-format-string-redundant-flag', prefix, *args)
         except backend.NonPortableConversion as exc:
             [s, c1, c2] = exc.args
             args = [c1, '=>', c2]
             if s != c1:
                 args += ['in', s]
             self.tag('c-format-string-non-portable-conversion', prefix,
                      *args)
     return fmt
示例#4
0
 def check_args(self,
                message,
                src_loc,
                src_fmt,
                dst_loc,
                dst_fmt,
                *,
                omitted_int_conv_ok=False):
     prefix = message_repr(message, template='{}:')
     src_args = src_fmt.arguments
     dst_args = dst_fmt.arguments
     if len(dst_args) > len(src_args):
         self.tag(
             'c-format-string-excess-arguments',
             prefix,
             len(dst_args),
             tags.safestr('({})'.format(dst_loc)),
             '>',
             len(src_args),
             tags.safestr('({})'.format(src_loc)),
         )
     elif len(dst_args) < len(src_args):
         if omitted_int_conv_ok:
             n_args_omitted = len(src_args) - len(dst_args)
             omitted_int_conv_ok = src_fmt.get_last_integer_conversion(
                 n=n_args_omitted)
         if not omitted_int_conv_ok:
             self.tag(
                 'c-format-string-missing-arguments',
                 prefix,
                 len(dst_args),
                 tags.safestr('({})'.format(dst_loc)),
                 '<',
                 len(src_args),
                 tags.safestr('({})'.format(src_loc)),
             )
     for src_arg, dst_arg in zip(src_args, dst_args):
         src_arg = src_arg[0]
         dst_arg = dst_arg[0]
         if src_arg.type != dst_arg.type:
             self.tag(
                 'c-format-string-argument-type-mismatch',
                 prefix,
                 tags.safestr(dst_arg.type),
                 tags.safestr('({})'.format(dst_loc)),
                 '!=',
                 tags.safestr(src_arg.type),
                 tags.safestr('({})'.format(src_loc)),
             )
示例#5
0
 def _check_message_xml_format(self, ctx, message, flags):
     if ctx.encoding is None:
         return
     prefix = message_repr(message, template='{}:')
     try:
         xml.check_fragment(message.msgid)
     except xml.SyntaxError as exc:
         if ctx.is_template:
             self.tag('malformed-xml', prefix, tags.safestr(exc))
         return
     if flags.fuzzy:
         return
     if not message.msgstr:
         return
     try:
         xml.check_fragment(message.msgstr)
     except xml.SyntaxError as exc:
         self.tag('malformed-xml', prefix, tags.safestr(exc))
示例#6
0
 def _check_message_flags(self, message):
     info = misc.Namespace()
     info.fuzzy = False
     info.range_min = 0
     info.range_max = 1e999  # +inf
     info.formats = None
     flags = collections.Counter(message.flags)
     wrap = None
     format_flags = collections.defaultdict(dict)
     range_flags = collections.defaultdict(collections.Counter)
     for flag, n in sorted(flags.items()):
         known_flag = True
         if flag == 'fuzzy':
             info.fuzzy = True
         elif flag in {'wrap', 'no-wrap'}:
             new_wrap = flag == 'wrap'
             if wrap == (not new_wrap):
                 self.tag('conflicting-message-flags',
                          message_repr(message, template='{}:'), 'wrap',
                          'no-wrap')
             else:
                 wrap = new_wrap
         elif flag.startswith('range:'):
             if message.msgid_plural is None:
                 self.tag('range-flag-without-plural-string')
             match = re.match(r'\A([0-9]+)[.][.]([0-9]+)\Z',
                              flag[6:].strip(' \t\r\f\v'))
             if match is not None:
                 i, j = map(int, match.groups())
                 if i < j:
                     info.range_min = i
                     info.range_max = j
                 else:
                     match = None
             if match is None:
                 self.tag('invalid-range-flag',
                          message_repr(message, template='{}:'), flag)
             else:
                 range_flags[i, j][flag] += n
                 n = 0
         elif flag.endswith('-format'):
             known_flag = False
             for prefix in 'no-', 'possible-', 'impossible-', '':
                 tp = prefix.rstrip('-')
                 if not flag.startswith(prefix):
                     continue
                 string_format = flag[len(prefix):-7]
                 if string_format in gettext.string_formats:
                     known_flag = True
                     format_flags[tp][string_format] = flag
                     break
         else:
             known_flag = False
         if not known_flag:
             self.tag('unknown-message-flag',
                      message_repr(message, template='{}:'), flag)
         if n > 1 and flag:
             self.tag('duplicate-message-flag',
                      message_repr(message, template='{}:'), flag)
     if len(range_flags) > 1:
         [range1, range2] = heapq.nsmallest(2, range_flags.keys())
         self.tag(
             'conflicting-message-flags',
             message_repr(message, template='{}:'),
             min(range_flags[range1].keys()),
             min(range_flags[range2].keys()),
         )
     elif len(range_flags) == 1:
         [range_flags] = range_flags.values()
         if sum(range_flags.values()) > 1:
             self.tag('duplicate-message-flag',
                      message_repr(message, template='{}:'),
                      min(range_flags.keys()))
     positive_format_flags = format_flags['']
     info.formats = frozenset(positive_format_flags)
     for fmt1, flag1 in sorted(positive_format_flags.items()):
         for fmt2, flag2 in sorted(positive_format_flags.items()):
             if fmt1 >= fmt2:
                 continue
             fmt_ex1 = gettext.string_formats[fmt1]
             fmt_ex2 = gettext.string_formats[fmt2]
             if fmt_ex1 & fmt_ex2:
                 # the formats are, at least to some extent, compatible
                 continue
             self.tag('conflicting-message-flags',
                      message_repr(message, template='{}:'), flag1, flag2)
     for positive_key, negative_key in [('', 'no'), ('', 'impossible'),
                                        ('possible', 'impossible')]:
         positive_format_flags = format_flags[positive_key]
         negative_format_flags = format_flags[negative_key]
         conflicting_formats = frozenset(positive_format_flags) & frozenset(
             negative_format_flags)
         for fmt in sorted(conflicting_formats):
             self.tag(
                 'conflicting-message-flags',
                 message_repr(message, template='{}:'),
                 positive_format_flags[fmt],
                 negative_format_flags[fmt],
             )
     positive_format_flags = format_flags['']
     possible_format_flags = format_flags['possible']
     redundant_formats = frozenset(positive_format_flags) & frozenset(
         possible_format_flags)
     for fmt in sorted(redundant_formats):
         self.tag(
             'redundant-message-flag', message_repr(message,
                                                    template='{}:'),
             possible_format_flags[fmt],
             tags.safe_format('(implied by {flag})'.format(
                 flag=positive_format_flags[fmt])))
     return info
示例#7
0
 def check_messages(self, ctx):
     found_unusual_characters = set()
     msgid_counter = collections.Counter()
     for message in ctx.file:
         if message.obsolete:
             continue
         if is_header_entry(message):
             continue
         flags = self._check_message_flags(message)
         self._check_message_formats(ctx, message, flags)
         msgid_counter[message.msgid, message.msgctxt] += 1
         if msgid_counter[message.msgid, message.msgctxt] == 2:
             self.tag('duplicate-message-definition', message_repr(message))
         has_msgstr = bool(message.msgstr)
         has_msgstr_plural = any(message.msgstr_plural.values())
         if ctx.is_template:
             if has_msgstr or has_msgstr_plural:
                 self.tag('translation-in-template', message_repr(message))
         leading_lf = message.msgid.startswith('\n')
         trailing_lf = message.msgid.endswith('\n')
         has_previous_msgid = any(s is not None for s in [
             message.previous_msgctxt,
             message.previous_msgid,
             message.previous_msgid_plural,
         ])
         if has_previous_msgid and not flags.fuzzy:
             self.tag('stray-previous-msgid', message_repr(message))
         strings = []
         if message.msgid_plural is not None:
             strings += [message.msgid_plural]
         if not flags.fuzzy:
             if has_msgstr:
                 strings += [message.msgstr]
             if has_msgstr_plural:
                 strings += message.msgstr_plural.values(
                 )  # the order doesn't matter here
         for s in strings:
             if s.startswith('\n') != leading_lf:
                 self.tag('inconsistent-leading-newlines',
                          message_repr(message))
                 break
         for s in strings:
             if s.endswith('\n') != trailing_lf:
                 self.tag('inconsistent-trailing-newlines',
                          message_repr(message))
                 break
         strings = []
         if has_msgstr:
             strings += [message.msgstr]
         if has_msgstr_plural:
             strings += misc.sorted_vk(message.msgstr_plural)
         if ctx.encoding is not None:
             msgid_uc = (set(find_unusual_characters(message.msgid)) | set(
                 find_unusual_characters(message.msgid_plural or '')))
             for msgstr in strings:
                 msgstr_uc = set(find_unusual_characters(msgstr))
                 uc = msgstr_uc - msgid_uc - found_unusual_characters
                 if not uc:
                     continue
                 names = ', '.join('U+{:04X} {}'.format(
                     ord(ch), encinfo.get_character_name(ch))
                                   for ch in sorted(uc))
                 self.tag('unusual-character-in-translation',
                          message_repr(message, template='{}:'),
                          tags.safestr(names))
                 found_unusual_characters |= uc
         if not flags.fuzzy:
             for msgstr in strings:
                 conflict_marker = gettext.search_for_conflict_marker(
                     msgstr)
                 if conflict_marker is not None:
                     conflict_marker = conflict_marker.group(0)
                     self.tag('conflict-marker-in-translation',
                              message_repr(message), conflict_marker)
                     break
             if has_msgstr_plural and not all(
                     message.msgstr_plural.values()):
                 self.tag('partially-translated-message',
                          message_repr(message))
     if len(msgid_counter) == 0:
         possible_hidden_strings = False
         if isinstance(ctx.file, polib.MOFile):
             possible_hidden_strings = ctx.file.possible_hidden_strings
         if not possible_hidden_strings:
             self.tag('empty-file')
示例#8
0
 def check_plurals(self, ctx):
     ctx.plural_preimage = None
     plural_forms = ctx.metadata['Plural-Forms']
     if len(plural_forms) > 1:
         self.tag('duplicate-header-field-plural-forms')
         plural_forms = sorted(set(plural_forms))
         if len(plural_forms) > 1:
             return
     if len(plural_forms) == 1:
         [plural_forms] = plural_forms
     else:
         assert len(plural_forms) == 0
         plural_forms = None
     correct_plural_forms = None
     if ctx.language is not None:
         correct_plural_forms = ctx.language.get_plural_forms()
     has_plurals = False  # messages with plural forms (translated or not)?
     expected_nplurals = {}  # number of plurals in _translated_ messages
     for message in ctx.file:
         if message.obsolete:
             continue
         if message.msgid_plural is not None:
             has_plurals = True
             if not message.translated():
                 continue
             expected_nplurals[len(message.msgstr_plural)] = message
             if len(expected_nplurals) > 1:
                 break
     if len(expected_nplurals) > 1:
         args = []
         for n, message in sorted(expected_nplurals.items()):
             args += [n, message_repr(message, template='({})'), '!=']
         self.tag('inconsistent-number-of-plural-forms', *args[:-1])
     if ctx.is_template:
         plural_forms_hint = 'nplurals=INTEGER; plural=EXPRESSION;'
     elif correct_plural_forms:
         plural_forms_hint = tags.safe_format(
             ' or '.join('{}' for s in correct_plural_forms),
             *correct_plural_forms)
     else:
         plural_forms_hint = 'nplurals=<n>; plural=<expression>'
     if plural_forms is None:
         if has_plurals:
             if expected_nplurals:
                 self.tag('no-required-plural-forms-header-field',
                          plural_forms_hint)
             else:
                 self.tag('no-plural-forms-header-field', plural_forms_hint)
         return
     if ctx.is_template:
         return
     try:
         (n, expr, ljunk, rjunk) = gettext.parse_plural_forms(plural_forms,
                                                              strict=False)
     except gettext.PluralFormsSyntaxError:
         if has_plurals:
             self.tag('syntax-error-in-plural-forms', plural_forms, '=>',
                      plural_forms_hint)
         else:
             self.tag('syntax-error-in-unused-plural-forms', plural_forms,
                      '=>', plural_forms_hint)
         return
     if ljunk:
         self.tag('leading-junk-in-plural-forms', ljunk)
     if rjunk:
         self.tag('trailing-junk-in-plural-forms', rjunk)
     if len(expected_nplurals) == 1:
         [expected_nplurals] = expected_nplurals.keys()
         if n != expected_nplurals:
             self.tag('incorrect-number-of-plural-forms', n,
                      tags.safestr('(Plural-Forms header field)'), '!=',
                      expected_nplurals,
                      tags.safestr('(number of msgstr items)'))
     locally_correct_n = locally_correct_expr = None
     if correct_plural_forms is not None:
         locally_correct_plural_forms = [
             (i, expression) for i, expression in map(
                 gettext.parse_plural_forms, correct_plural_forms) if i == n
         ]
         if not locally_correct_plural_forms:
             if has_plurals:
                 self.tag('unusual-plural-forms', plural_forms, '=>',
                          plural_forms_hint)
             else:
                 self.tag('unusual-unused-plural-forms', plural_forms, '=>',
                          plural_forms_hint)
         elif len(locally_correct_plural_forms) == 1:
             [[locally_correct_n,
               locally_correct_expr]] = locally_correct_plural_forms
     plural_preimage = collections.defaultdict(list)
     unusual_plural_forms = False
     codomain_limit = 200
     try:
         for i in range(codomain_limit):
             fi = expr(i)
             if fi >= n:
                 message = tags.safe_format('f({}) = {} >= {}'.format(
                     i, fi, n))
                 if has_plurals:
                     self.tag('codomain-error-in-plural-forms', message)
                 else:
                     self.tag('codomain-error-in-unused-plural-forms',
                              message)
                 break
             plural_preimage[fi] += [i]
             if (n == locally_correct_n) and (fi !=
                                              locally_correct_expr(i)) and (
                                                  not unusual_plural_forms):
                 if has_plurals:
                     self.tag('unusual-plural-forms', plural_forms, '=>',
                              plural_forms_hint)
                 else:
                     self.tag('unusual-unused-plural-forms', plural_forms,
                              '=>', plural_forms_hint)
                 unusual_plural_forms = True
         else:
             ctx.plural_preimage = dict(plural_preimage)
     except OverflowError:
         message = tags.safe_format('f({}): integer overflow', i)
         if has_plurals:
             self.tag('arithmetic-error-in-plural-forms', message)
         else:
             self.tag('arithmetic-error-in-unused-plural-forms', message)
     except ZeroDivisionError:
         message = tags.safe_format('f({}): division by zero', i)
         if has_plurals:
             self.tag('arithmetic-error-in-plural-forms', message)
         else:
             self.tag('arithmetic-error-in-unused-plural-forms', message)
     codomain = expr.codomain()
     if codomain is not None:
         (x, y) = codomain
         uncov_rngs = []
         if x > 0:
             uncov_rngs += [range(0, x)]
         if y + 1 < n:
             uncov_rngs += [range(y + 1, n)]
     if (not uncov_rngs) and (ctx.plural_preimage is not None):
         period = expr.period()
         if period is None:
             period = (0, 1e999)
         if sum(period) < codomain_limit:
             for i in sorted(ctx.plural_preimage):
                 if (i > 0) and (i - 1 not in ctx.plural_preimage):
                     uncov_rngs += [range(i - 1, i)]
                     break
                 if (i + 1 < n) and (i + 1 not in ctx.plural_preimage):
                     uncov_rngs += [range(i + 1, i + 2)]
                     break
     for rng in uncov_rngs:
         rng = misc.format_range(rng, max=5)
         message = tags.safestr('f(x) != {}'.format(rng))
         if has_plurals:
             self.tag('codomain-error-in-plural-forms', message)
         else:
             self.tag('codomain-error-in-unused-plural-forms', message)
         ctx.plural_preimage = None
示例#9
0
 def check_args(self,
                message,
                src_loc,
                src_fmt,
                dst_loc,
                dst_fmt,
                *,
                omitted_int_conv_ok=False):
     prefix = message_repr(message, template='{}:')
     # unnamed arguments:
     src_args = src_fmt.seq_arguments
     dst_args = dst_fmt.seq_arguments
     if len(dst_args) != len(src_args):
         self.tag(
             'python-format-string-argument-number-mismatch',
             prefix,
             len(dst_args),
             tags.safestr('({})'.format(dst_loc)),
             '!=',
             len(src_args),
             tags.safestr('({})'.format(src_loc)),
         )
     for src_arg, dst_arg in zip(src_args, dst_args):
         if src_arg.type != dst_arg.type:
             self.tag(
                 'python-format-string-argument-type-mismatch',
                 prefix,
                 tags.safestr(dst_arg.type),
                 tags.safestr('({})'.format(dst_loc)),
                 '!=',
                 tags.safestr(src_arg.type),
                 tags.safestr('({})'.format(src_loc)),
             )
     # named arguments:
     src_args = src_fmt.map_arguments
     dst_args = dst_fmt.map_arguments
     for key in sorted(dst_args.keys() & src_args.keys()):
         src_arg = src_args[key][0]
         dst_arg = dst_args[key][0]
         if src_arg.type != dst_arg.type:
             self.tag(
                 'python-format-string-argument-type-mismatch',
                 prefix,
                 tags.safestr(dst_arg.type),
                 tags.safestr('({})'.format(dst_loc)),
                 '!=',
                 tags.safestr(src_arg.type),
                 tags.safestr('({})'.format(src_loc)),
             )
     for key in sorted(dst_args.keys() - src_args.keys()):
         self.tag(
             'python-format-string-unknown-argument',
             prefix,
             key,
             tags.safestr('in'),
             tags.safestr(dst_loc),
             tags.safestr('but not in'),
             tags.safestr(src_loc),
         )
     missing_keys = src_args.keys() - dst_args.keys()
     if len(missing_keys) == 1 and omitted_int_conv_ok:
         [missing_key] = missing_keys
         if all(arg.type == 'int' for arg in src_args[missing_key]):
             missing_keys = set()
     for key in sorted(missing_keys):
         self.tag(
             'python-format-string-missing-argument',
             prefix,
             key,
             tags.safestr('not in'),
             tags.safestr(dst_loc),
             tags.safestr('while in'),
             tags.safestr(src_loc),
         )