115 lines
4 KiB
Python
115 lines
4 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from collections import Counter
|
|
import os
|
|
|
|
from compare_locales import parser, checks
|
|
from compare_locales.paths import File, REFERENCE_LOCALE
|
|
|
|
|
|
class L10nLinter:
|
|
def lint(self, files, get_reference_and_tests):
|
|
results = []
|
|
for path in files:
|
|
if not parser.hasParser(path):
|
|
continue
|
|
ref, extra_tests = get_reference_and_tests(path)
|
|
results.extend(self.lint_file(path, ref, extra_tests))
|
|
return results
|
|
|
|
def lint_file(self, path, ref, extra_tests):
|
|
file_parser = parser.getParser(path)
|
|
if ref is not None and os.path.isfile(ref):
|
|
file_parser.readFile(ref)
|
|
reference = file_parser.parse()
|
|
else:
|
|
reference = {}
|
|
file_parser.readFile(path)
|
|
current = file_parser.parse()
|
|
checker = checks.getChecker(
|
|
File(path, path, locale=REFERENCE_LOCALE), extra_tests=extra_tests
|
|
)
|
|
if checker and checker.needs_reference:
|
|
checker.set_reference(current)
|
|
linter = EntityLinter(current, checker, reference)
|
|
for current_entity in current:
|
|
for result in linter.lint_entity(current_entity):
|
|
result["path"] = path
|
|
yield result
|
|
|
|
|
|
class EntityLinter:
|
|
"""Factored out helper to run linters on a single entity."""
|
|
|
|
def __init__(self, current, checker, reference):
|
|
self.key_count = Counter(entity.key for entity in current)
|
|
self.checker = checker
|
|
self.reference = reference
|
|
|
|
def lint_entity(self, current_entity):
|
|
res = self.handle_junk(current_entity)
|
|
if res:
|
|
yield res
|
|
return
|
|
for res in self.lint_full_entity(current_entity):
|
|
yield res
|
|
for res in self.lint_value(current_entity):
|
|
yield res
|
|
|
|
def lint_full_entity(self, current_entity):
|
|
"""Checks that go good or bad for a full entity,
|
|
without a particular spot inside the entity.
|
|
"""
|
|
lineno = col = None
|
|
if self.key_count[current_entity.key] > 1:
|
|
lineno, col = current_entity.position()
|
|
yield {
|
|
"lineno": lineno,
|
|
"column": col,
|
|
"level": "error",
|
|
"message": f"Duplicate string with ID: {current_entity.key}",
|
|
}
|
|
|
|
if current_entity.key in self.reference:
|
|
reference_entity = self.reference[current_entity.key]
|
|
if not current_entity.equals(reference_entity):
|
|
if lineno is None:
|
|
lineno, col = current_entity.position()
|
|
msg = "Changes to string require a new ID: {}".format(
|
|
current_entity.key
|
|
)
|
|
yield {
|
|
"lineno": lineno,
|
|
"column": col,
|
|
"level": "warning",
|
|
"message": msg,
|
|
}
|
|
|
|
def lint_value(self, current_entity):
|
|
"""Checks that error on particular locations in the entity value."""
|
|
if self.checker:
|
|
for tp, pos, msg, cat in self.checker.check(current_entity, current_entity):
|
|
if isinstance(pos, checks.EntityPos):
|
|
lineno, col = current_entity.position(pos)
|
|
else:
|
|
lineno, col = current_entity.value_position(pos)
|
|
yield {
|
|
"lineno": lineno,
|
|
"column": col,
|
|
"level": tp,
|
|
"message": msg,
|
|
}
|
|
|
|
def handle_junk(self, current_entity):
|
|
if not isinstance(current_entity, parser.Junk):
|
|
return None
|
|
|
|
lineno, col = current_entity.position()
|
|
return {
|
|
"lineno": lineno,
|
|
"column": col,
|
|
"level": "error",
|
|
"message": current_entity.error_message(),
|
|
}
|