Report duplicate object IDs

This commit is contained in:
James Westman 2021-11-03 14:58:33 -05:00
parent dfb09b9357
commit 241668fb94
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
5 changed files with 68 additions and 1 deletions

View file

@ -51,6 +51,11 @@ class UI(AstNode):
return gir_ctx
@lazy_prop
def objects_by_id(self):
return { obj.id: obj for obj in self.iterate_children_recursive() if hasattr(obj, "id") }
@validate()
def gir_errors(self):
# make sure gir is loaded
@ -66,6 +71,19 @@ class UI(AstNode):
self.children[Template][1].group.start)
@validate()
def unique_ids(self):
passed = {}
for obj in self.iterate_children_recursive():
if obj.tokens["id"] is None:
continue
if obj.tokens["id"] in passed:
token = obj.group.tokens["id"]
raise CompileError(f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end)
passed[obj.tokens["id"]] = obj
def emit_xml(self, xml: XmlEmitter):
xml.start_tag("interface")
for x in self.children:

View file

@ -118,6 +118,12 @@ class AstNode:
yield from child.get_semantic_tokens()
def iterate_children_recursive(self) -> T.Iterator["AstNode"]:
yield self
for child in self.children:
yield from child.iterate_children_recursive()
def validate(token_name=None, end_token_name=None, skip_incomplete=False):
""" Decorator for functions that validate an AST node. Exceptions raised
during validation are marked with range information from the tokens. Also

View file

@ -0,0 +1,4 @@
using Gtk 4.0;
Gtk.Label label {}
Gtk.Label label {}

View file

@ -0,0 +1 @@
4,11,5,Duplicate object ID 'label'

View file

@ -24,8 +24,9 @@ import traceback
import unittest
from gtkblueprinttool import tokenizer, parser
from gtkblueprinttool.errors import PrintableError, MultipleErrors
from gtkblueprinttool.errors import PrintableError, MultipleErrors, CompileError
from gtkblueprinttool.tokenizer import Token, TokenType, tokenize
from gtkblueprinttool import utils
class TestSamples(unittest.TestCase):
@ -54,6 +55,39 @@ class TestSamples(unittest.TestCase):
raise AssertionError()
def assert_sample_error(self, name):
try:
with open((Path(__file__).parent / f"sample_errors/{name}.blp").resolve()) as f:
blueprint = f.read()
with open((Path(__file__).parent / f"sample_errors/{name}.err").resolve()) as f:
expected = f.read()
tokens = tokenizer.tokenize(blueprint)
ast, errors = parser.parse(tokens)
if errors:
raise errors
if len(ast.errors):
raise MultipleErrors(ast.errors)
except PrintableError as e:
def error_str(error):
line, col = utils.idx_to_pos(error.start, blueprint)
len = error.end - error.start
return ",".join([str(line + 1), str(col), str(len), error.message])
if isinstance(e, CompileError):
actual = error_str(e)
elif isinstance(e, MultipleErrors):
actual = "\n".join([error_str(error) for error in e.errors])
else:
raise AssertionError()
if actual.strip() != expected.strip():
diff = difflib.unified_diff(expected.splitlines(), actual.splitlines())
print("\n".join(diff))
raise AssertionError()
def test_samples(self):
self.assert_sample("binding")
self.assert_sample("child_type")
@ -66,3 +100,7 @@ class TestSamples(unittest.TestCase):
self.assert_sample("style")
self.assert_sample("template")
self.assert_sample("using")
def test_sample_errors(self):
self.assert_sample_error("duplicate_obj_id")