Codegen: Make public AST classes final

This commit is contained in:
Tom Hvitved
2024-09-13 09:39:22 +02:00
parent 39ce3fbd33
commit 9c0cafeeb8
17 changed files with 224 additions and 122 deletions

View File

@@ -2,18 +2,19 @@
QL code generation
`generate(opts, renderer)` will generate in the library directory:
* generated/Raw.qll with thin class wrappers around DB types
* generated/Synth.qll with the base algebraic datatypes for AST entities
* generated/<group>/<Class>.qll with generated properties for each class
* if not already modified, a elements/<group>/<Class>.qll stub to customize the above classes
* elements.qll importing all the above stubs
* if not already modified, a elements/<group>/<Class>Constructor.qll stub to customize the algebraic datatype
* `generated/Raw.qll` with thin class wrappers around DB types
* `generated/Synth.qll` with the base algebraic datatypes for AST entities
* `generated/<group>/<Class>.qll` with generated properties for each class
* if not already modified, an `elements/<group>/<Class>Impl.qll` stub to customize the above classes
* `elements/<group>/<Class>.qll` that wraps the internal `<Class>Impl.qll` file in a public `final` class.
* `elements.qll` importing all the above public classes
* if not already modified, an `elements/<group>/<Class>Constructor.qll` stub to customize the algebraic datatype
characteristic predicate
* generated/SynthConstructors.qll importing all the above constructor stubs
* generated/PureSynthConstructors.qll importing constructor stubs for pure synthesized types (that is, not
* `generated/SynthConstructors.qll` importing all the above constructor stubs
* `generated/PureSynthConstructors.qll` importing constructor stubs for pure synthesized types (that is, not
corresponding to raw types)
Moreover in the test directory for each <Class> in <group> it will generate beneath the
extractor-tests/generated/<group>/<Class> directory either
`extractor-tests/generated/<group>/<Class>` directory either
* a `MISSING_SOURCE.txt` explanation file if no source is present, or
* one `<Class>.ql` test query for all single properties and on `<Class>_<property>.ql` test query for each optional or
repeated property
@@ -164,6 +165,7 @@ def get_ql_class(cls: schema.Class, lookup: typing.Dict[str, schema.Class]) -> q
return ql.Class(
name=cls.name,
bases=cls.bases,
bases_impl=[base + "Impl::" + base for base in cls.bases],
final=not cls.derived,
properties=properties,
dir=pathlib.Path(cls.group or ""),
@@ -210,15 +212,17 @@ def get_import(file: pathlib.Path, root_dir: pathlib.Path):
return str(stem).replace("/", ".")
def get_types_used_by(cls: ql.Class) -> typing.Iterable[str]:
def get_types_used_by(cls: ql.Class, is_impl: bool) -> typing.Iterable[str]:
for b in cls.bases:
yield b.base
yield b.base + "Impl" if is_impl else b.base
for p in cls.properties:
yield p.type
if cls.root:
yield cls.name # used in `getResolveStep` and `resolve`
def get_classes_used_by(cls: ql.Class) -> typing.List[str]:
return sorted(set(t for t in get_types_used_by(cls) if t[0].isupper() and t != cls.name))
def get_classes_used_by(cls: ql.Class, is_impl: bool) -> typing.List[str]:
return sorted(set(t for t in get_types_used_by(cls, is_impl) if t[0].isupper() and (is_impl or t != cls.name)))
def format(codeql, files):
@@ -239,6 +243,10 @@ def _get_path(cls: schema.Class) -> pathlib.Path:
return pathlib.Path(cls.group or "", cls.name).with_suffix(".qll")
def _get_path_impl(cls: schema.Class) -> pathlib.Path:
return pathlib.Path(cls.group or "", cls.name+"Impl").with_suffix(".qll")
def _get_all_properties(cls: schema.Class, lookup: typing.Dict[str, schema.Class],
already_seen: typing.Optional[typing.Set[int]] = None) -> \
typing.Iterable[typing.Tuple[schema.Class, schema.Property]]:
@@ -315,11 +323,13 @@ def _get_stub(cls: schema.Class, base_import: str, generated_import_prefix: str)
else:
accessors = []
return ql.Stub(name=cls.name, base_import=base_import, import_prefix=generated_import_prefix,
doc=cls.doc, synth_accessors=accessors,
internal="ql_internal" in cls.pragmas)
doc=cls.doc, synth_accessors=accessors)
def _get_class_public(cls: schema.Class) -> ql.ClassPublic:
return ql.ClassPublic(name=cls.name, doc=cls.doc, internal="ql_internal" in cls.pragmas)
_stub_qldoc_header = "// the following QLdoc is generated: if you need to edit it, do it in the schema file\n"
_stub_qldoc_header = "// the following QLdoc is generated: if you need to edit it, do it in the schema file\n "
_class_qldoc_re = re.compile(
rf"(?P<qldoc>(?:{re.escape(_stub_qldoc_header)})?/\*\*.*?\*/\s*|^\s*)(?:class\s+(?P<class>\w+))?",
@@ -330,13 +340,13 @@ def _patch_class_qldoc(cls: str, qldoc: str, stub_file: pathlib.Path):
""" Replace or insert `qldoc` as the QLdoc of class `cls` in `stub_file` """
if not qldoc or not stub_file.exists():
return
qldoc = "\n".join(l.rstrip() for l in qldoc.splitlines())
qldoc = "\n ".join(l.rstrip() for l in qldoc.splitlines())
with open(stub_file) as input:
contents = input.read()
for match in _class_qldoc_re.finditer(contents):
if match["class"] == cls:
qldoc_start, qldoc_end = match.span("qldoc")
contents = f"{contents[:qldoc_start]}{_stub_qldoc_header}{qldoc}\n{contents[qldoc_end:]}"
contents = f"{contents[:qldoc_start]}{_stub_qldoc_header}{qldoc}\n {contents[qldoc_end:]}"
tmp = stub_file.with_suffix(f"{stub_file.suffix}.bkp")
with open(tmp, "w") as out:
out.write(contents)
@@ -370,6 +380,8 @@ def generate(opts, renderer):
raise RootElementHasChildren(root)
imports = {}
imports_impl = {}
classes_used_by = {}
generated_import_prefix = get_import(out, opts.root_dir)
registry = opts.generated_registry or pathlib.Path(
os.path.commonpath((out, stub_out, test_out)), ".generated.list")
@@ -382,24 +394,33 @@ def generate(opts, renderer):
classes_by_dir_and_name = sorted(classes.values(), key=lambda cls: (cls.dir, cls.name))
for c in classes_by_dir_and_name:
imports[c.name] = get_import(stub_out / c.path, opts.root_dir)
path = get_import(stub_out / c.path, opts.root_dir)
imports[c.name] = path
imports_impl[c.name + "Impl"] = path + "Impl"
for c in classes.values():
qll = out / c.path.with_suffix(".qll")
c.imports = [imports[t] for t in get_classes_used_by(c)]
c.imports = [imports[t] if t in imports else imports_impl[t]+ "::Impl as " + t for t in get_classes_used_by(c, is_impl=True)]
classes_used_by[c.name] = get_classes_used_by(c, is_impl=False)
c.import_prefix = generated_import_prefix
renderer.render(c, qll)
for c in data.classes.values():
path = _get_path(c)
stub_file = stub_out / path
path_impl = _get_path_impl(c)
stub_file = stub_out / path_impl
base_import = get_import(out / path, opts.root_dir)
stub = _get_stub(c, base_import, generated_import_prefix)
if not renderer.is_customized_stub(stub_file):
renderer.render(stub, stub_file)
else:
qldoc = renderer.render_str(stub, template='ql_stub_class_qldoc')
_patch_class_qldoc(c.name, qldoc, stub_file)
class_public = _get_class_public(c)
class_public_file = stub_out / path
class_public.imports = [imports[t] for t in classes_used_by[c.name]]
renderer.render(class_public, class_public_file)
# for example path/to/elements -> path/to/elements.qll
renderer.render(ql.ImportList([i for name, i in imports.items() if not classes[name].internal]),

View File

@@ -101,6 +101,7 @@ class Class:
name: str
bases: List[Base] = field(default_factory=list)
bases_impl: List[Base] = field(default_factory=list)
final: bool = False
properties: List[Property] = field(default_factory=list)
dir: pathlib.Path = pathlib.Path()
@@ -115,6 +116,8 @@ class Class:
def __post_init__(self):
self.bases = [Base(str(b), str(prev)) for b, prev in zip(self.bases, itertools.chain([""], self.bases))]
self.bases_impl = [Base(str(b), str(prev)) for b, prev in zip(
self.bases_impl, itertools.chain([""], self.bases_impl))]
if self.properties:
self.properties[0].first = True
@@ -159,13 +162,26 @@ class Stub:
base_import: str
import_prefix: str
synth_accessors: List[SynthUnderlyingAccessor] = field(default_factory=list)
internal: bool = False
doc: List[str] = field(default_factory=list)
@property
def has_synth_accessors(self) -> bool:
return bool(self.synth_accessors)
@property
def has_qldoc(self) -> bool:
return bool(self.doc)
@dataclass
class ClassPublic:
template: ClassVar = 'ql_class_public'
name: str
imports: List[str] = field(default_factory=list)
internal: bool = False
doc: List[str] = field(default_factory=list)
@property
def has_qldoc(self) -> bool:
return bool(self.doc) or self.internal

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
{{#includes}}
// from {{src}}

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
/**
* This module provides the generated definition of `{{name}}`.
* INTERNAL: Do not import directly.
@@ -22,7 +22,7 @@ module Generated {
* INTERNAL: Do not reference the `Generated::{{name}}` class directly.
* Use the subclass `{{name}}`, where the following predicates are available.
*/
class {{name}} extends Synth::T{{name}}{{#bases}}, {{.}}{{/bases}} {
class {{name}} extends Synth::T{{name}}{{#bases_impl}}, {{.}}{{/bases_impl}} {
{{#root}}
/**
* Gets the string representation of this element.

View File

@@ -0,0 +1,12 @@
// generated by {{generator}}, do not edit
/**
* This module provides the public class `{{name}}`.
*/
private import {{name}}Impl
{{#imports}}
import {{.}}
{{/imports}}
{{>ql_stub_class_qldoc}}
final class {{name}} = Impl::{{name}};

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
/**
* This module exports all modules providing `Element` subclasses.
*/

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
/**
* This module provides the generated parent/child relationship.
*/

View File

@@ -6,10 +6,16 @@ private import {{import_prefix}}.Raw
private import {{import_prefix}}.Synth
{{/has_synth_accessors}}
{{>ql_stub_class_qldoc}}
class {{name}} extends Generated::{{name}} {
/**
* INTERNAL: This module contains the customizable definition of `{{name}}` and should not
* be referenced directly.
*/
module Impl {
{{>ql_stub_class_qldoc}}
class {{name}} extends Generated::{{name}} {
{{#synth_accessors}}
private
cached {{type}} getUnderlying{{argument}}() { this = Synth::T{{name}}({{#constructorparams}}{{^first}},{{/first}}{{param}}{{/constructorparams}})}
{{/synth_accessors}}
}
}
}

View File

@@ -1,6 +1,5 @@
/**
* This module provides a hand-modifiable wrapper around the generated class `{{name}}`.
{{#internal}}
*
* INTERNAL: Do not use.
{{/internal}}
*/

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
import {{elements_module}}
import TestUtils

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
After a source file is added in this directory and {{generator}} is run again, test queries
will appear and this file will be deleted

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
import {{elements_module}}
import TestUtils

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
#![cfg_attr(any(), rustfmt::skip)]

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
{{#modules}}
mod {{.}};

View File

@@ -1,4 +1,4 @@
// generated by {{generator}}
// generated by {{generator}}, do not edit
{{#function}}
fn {{name}}{{signature}} {

View File

@@ -147,15 +147,14 @@ def test_class_with_children():
assert cls.has_children is True
@pytest.mark.parametrize("doc,internal,expected",
@pytest.mark.parametrize("doc,expected",
[
(["foo", "bar"], False, True),
(["foo", "bar"], True, True),
([], False, False),
([], True, True),
(["foo", "bar"], True),
(["foo", "bar"], True),
([], False)
])
def test_has_doc(doc, internal, expected):
stub = ql.Stub("Class", base_import="foo", import_prefix="bar", doc=doc, internal=internal)
def test_has_doc(doc, expected):
stub = ql.Stub("Class", base_import="foo", import_prefix="bar", doc=doc)
assert stub.has_qldoc is expected

View File

@@ -114,7 +114,9 @@ def _filter_generated_classes(ret, output_test_files=False):
("Raw", "Synth", "SynthConstructors", "PureSynthConstructors")}
assert base_files <= stub_files
return {
str(f): (ret[stub_path() / f], ret[ql_output_path() / f])
str(f): (ret[stub_path() / f],
ret[stub_path() / pathlib.Path(f.parent, f.stem + "Impl.qll")],
ret[ql_output_path() / f])
for f in base_files
}
@@ -144,12 +146,17 @@ def a_ql_stub(*, name, import_prefix="", **kwargs):
base_import=f"{gen_import_prefix}{import_prefix}{name}")
def a_ql_class_public(*, name, **kwargs):
return ql.ClassPublic(name=name, **kwargs)
def test_one_empty_class(generate_classes):
assert generate_classes([
schema.Class("A")
]) == {
"A.qll": (a_ql_stub(name="A"),
a_ql_class(name="A", final=True)),
"A.qll": (a_ql_class_public(name="A"),
a_ql_stub(name="A"),
a_ql_class(name="A", final=True, imports=[stub_import_prefix + "A"]))
}
@@ -157,8 +164,9 @@ def test_one_empty_internal_class(generate_classes):
assert generate_classes([
schema.Class("A", pragmas=["ql_internal"])
]) == {
"A.qll": (a_ql_stub(name="A", internal=True),
a_ql_class(name="A", final=True, internal=True)),
"A.qll": (a_ql_class_public(name="A", internal=True),
a_ql_stub(name="A"),
a_ql_class(name="A", final=True, internal=True, imports=[stub_import_prefix + "A"])),
}
@@ -169,11 +177,11 @@ def test_hierarchy(generate_classes):
schema.Class("B", bases=["A"], derived={"D"}),
schema.Class("A", derived={"B", "C"}),
]) == {
"A.qll": (a_ql_stub(name="A"), a_ql_class(name="A")),
"B.qll": (a_ql_stub(name="B"), a_ql_class(name="B", bases=["A"], imports=[stub_import_prefix + "A"])),
"C.qll": (a_ql_stub(name="C"), a_ql_class(name="C", bases=["A"], imports=[stub_import_prefix + "A"])),
"D.qll": (a_ql_stub(name="D"), a_ql_class(name="D", final=True, bases=["B", "C"],
imports=[stub_import_prefix + cls for cls in "BC"])),
"A.qll": (a_ql_class_public(name="A"), a_ql_stub(name="A"), a_ql_class(name="A", imports=[stub_import_prefix + "A"])),
"B.qll": (a_ql_class_public(name="B", imports=[stub_import_prefix + "A"]), a_ql_stub(name="B"), a_ql_class(name="B", bases=["A"], bases_impl=["AImpl::A"], imports=[stub_import_prefix + "AImpl::Impl as AImpl"])),
"C.qll": (a_ql_class_public(name="C", imports=[stub_import_prefix + "A"]), a_ql_stub(name="C"), a_ql_class(name="C", bases=["A"], bases_impl=["AImpl::A"], imports=[stub_import_prefix + "AImpl::Impl as AImpl"])),
"D.qll": (a_ql_class_public(name="D", imports=[stub_import_prefix + "B", stub_import_prefix + "C"]), a_ql_stub(name="D"), a_ql_class(name="D", final=True, bases=["B", "C"], bases_impl=["BImpl::B", "CImpl::C"],
imports=[stub_import_prefix + cls + "Impl::Impl as " + cls + "Impl" for cls in "BC"])),
}
@@ -202,13 +210,13 @@ def test_hierarchy_children(generate_children_implementations):
schema.Class("C", bases=["A"], derived={"D"}, pragmas=["ql_internal"]),
schema.Class("D", bases=["B", "C"]),
]) == ql.GetParentImplementation(
classes=[a_ql_class(name="A", internal=True),
a_ql_class(name="B", bases=["A"], imports=[
stub_import_prefix + "A"]),
a_ql_class(name="C", bases=["A"], imports=[
stub_import_prefix + "A"], internal=True),
a_ql_class(name="D", final=True, bases=["B", "C"],
imports=[stub_import_prefix + cls for cls in "BC"]),
classes=[a_ql_class(name="A", internal=True, imports=[stub_import_prefix + "A"]),
a_ql_class(name="B", bases=["A"], bases_impl=["AImpl::A"], imports=[
stub_import_prefix + "AImpl::Impl as AImpl"]),
a_ql_class(name="C", bases=["A"], bases_impl=["AImpl::A"], imports=[
stub_import_prefix + "AImpl::Impl as AImpl"], internal=True),
a_ql_class(name="D", final=True, bases=["B", "C"], bases_impl=["BImpl::B", "CImpl::C"],
imports=[stub_import_prefix + cls + "Impl::Impl as " + cls + "Impl" for cls in "BC"]),
],
imports=[stub_import] + [stub_import_prefix + cls for cls in "AC"],
)
@@ -219,12 +227,14 @@ def test_single_property(generate_classes):
schema.Class("MyObject", properties=[
schema.SingleProperty("foo", "bar")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
tableparams=["this", "result"], doc="foo of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -233,13 +243,15 @@ def test_internal_property(generate_classes):
schema.Class("MyObject", properties=[
schema.SingleProperty("foo", "bar", pragmas=["ql_internal"])]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
tableparams=["this", "result"], doc="foo of this my object",
internal=True),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -257,8 +269,9 @@ def test_children(generate_classes):
schema.RepeatedOptionalProperty("child_4", "int", is_child=True),
]),
]) == {
"FakeRoot.qll": (a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True)),
"MyObject.qll": (a_ql_stub(name="MyObject"),
"FakeRoot.qll": (a_ql_class_public(name="FakeRoot"), a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"])),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="A", type="int", tablename="my_objects",
@@ -294,7 +307,8 @@ def test_children(generate_classes):
tableparams=["this", "index", "result"], is_optional=True,
prev_child="Child3", doc="child 4 of this my object",
doc_plural="child 4s of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -306,7 +320,8 @@ def test_single_properties(generate_classes):
schema.SingleProperty("three", "z"),
]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="One", type="x", tablename="my_objects",
@@ -318,7 +333,8 @@ def test_single_properties(generate_classes):
ql.Property(singular="Three", type="z", tablename="my_objects",
tableparams=["this", "_", "_", "result"],
doc="three of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -329,13 +345,15 @@ def test_optional_property(generate_classes, is_child, prev_child):
schema.Class("MyObject", properties=[
schema.OptionalProperty("foo", "bar", is_child=is_child)]),
]) == {
"FakeRoot.qll": (a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True)),
"MyObject.qll": (a_ql_stub(name="MyObject"),
"FakeRoot.qll": (a_ql_class_public(name="FakeRoot"), a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"])),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="Foo", type="bar", tablename="my_object_foos",
tableparams=["this", "result"],
is_optional=True, prev_child=prev_child, doc="foo of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -346,13 +364,15 @@ def test_repeated_property(generate_classes, is_child, prev_child):
schema.Class("MyObject", properties=[
schema.RepeatedProperty("foo", "bar", is_child=is_child)]),
]) == {
"FakeRoot.qll": (a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True)),
"MyObject.qll": (a_ql_stub(name="MyObject"),
"FakeRoot.qll": (a_ql_class_public(name="FakeRoot"), a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"])),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="Foo", plural="Foos", type="bar", tablename="my_object_foos",
tableparams=["this", "index", "result"], prev_child=prev_child,
doc="foo of this my object", doc_plural="foos of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -362,13 +382,15 @@ def test_repeated_unordered_property(generate_classes):
schema.Class("MyObject", properties=[
schema.RepeatedUnorderedProperty("foo", "bar")]),
]) == {
"FakeRoot.qll": (a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True)),
"MyObject.qll": (a_ql_stub(name="MyObject"),
"FakeRoot.qll": (a_ql_class_public(name="FakeRoot"), a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"])),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="Foo", plural="Foos", type="bar", tablename="my_object_foos",
tableparams=["this", "result"], is_unordered=True,
doc="foo of this my object", doc_plural="foos of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -380,14 +402,16 @@ def test_repeated_optional_property(generate_classes, is_child, prev_child):
schema.RepeatedOptionalProperty("foo", "bar", is_child=is_child)]),
]) == {
"FakeRoot.qll": (a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True)),
"MyObject.qll": (a_ql_stub(name="MyObject"),
"FakeRoot.qll": (a_ql_class_public(name="FakeRoot"), a_ql_stub(name="FakeRoot"), a_ql_class(name="FakeRoot", final=True, imports=[stub_import_prefix + "FakeRoot"])),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="Foo", plural="Foos", type="bar", tablename="my_object_foos",
tableparams=["this", "index", "result"], is_optional=True,
prev_child=prev_child, doc="foo of this my object",
doc_plural="foos of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -396,11 +420,13 @@ def test_predicate_property(generate_classes):
schema.Class("MyObject", properties=[
schema.PredicateProperty("is_foo")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="isFoo", type="predicate", tablename="my_object_is_foo",
tableparams=["this"], is_predicate=True, doc="this my object is foo"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -411,16 +437,17 @@ def test_single_class_property(generate_classes, is_child, prev_child):
schema.Class("MyObject", properties=[
schema.SingleProperty("foo", "Bar", is_child=is_child)]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject", imports=[stub_import_prefix + "Bar"]),
a_ql_stub(name="MyObject"),
a_ql_class(
name="MyObject", final=True, imports=[stub_import_prefix + "Bar"], properties=[
name="MyObject", final=True, imports=[stub_import_prefix + "Bar", stub_import_prefix + "MyObject"], properties=[
ql.Property(singular="Foo", type="Bar", tablename="my_objects",
tableparams=[
"this", "result"],
prev_child=prev_child, doc="foo of this my object"),
],
)),
"Bar.qll": (a_ql_stub(name="Bar"), a_ql_class(name="Bar", final=True)),
"Bar.qll": (a_ql_class_public(name="Bar"), a_ql_stub(name="Bar"), a_ql_class(name="Bar", final=True, imports=[stub_import_prefix + "Bar"])),
}
@@ -429,7 +456,7 @@ def test_class_with_doc(generate_classes):
assert generate_classes([
schema.Class("A", doc=doc),
]) == {
"A.qll": (a_ql_stub(name="A", doc=doc), a_ql_class(name="A", final=True, doc=doc)),
"A.qll": (a_ql_class_public(name="A", doc=doc), a_ql_stub(name="A", doc=doc), a_ql_class(name="A", final=True, doc=doc, imports=[stub_import_prefix + "A"])),
}
@@ -440,10 +467,11 @@ def test_class_dir(generate_classes):
schema.Class("B", bases=["A"]),
]) == {
f"{dir}/A.qll": (
a_ql_stub(name="A", import_prefix="another.rel.path."), a_ql_class(name="A", dir=pathlib.Path(dir))),
"B.qll": (a_ql_stub(name="B"),
a_ql_class(name="B", final=True, bases=["A"],
imports=[stub_import_prefix + "another.rel.path.A"])),
a_ql_class_public(name="A"), a_ql_stub(name="A", import_prefix="another.rel.path."), a_ql_class(name="A", dir=pathlib.Path(dir), imports=[stub_import_prefix + "another.rel.path.A"])),
"B.qll": (a_ql_class_public(name="B", imports=[stub_import_prefix + "another.rel.path.A"]),
a_ql_stub(name="B"),
a_ql_class(name="B", final=True, bases=["A"], bases_impl=["AImpl::A"],
imports=[stub_import_prefix + "another.rel.path.AImpl::Impl as AImpl"])),
}
@@ -518,7 +546,7 @@ def test_manage_parameters(opts, generate, renderer, force):
def test_modified_stub_skipped(qlgen_opts, generate, render_manager):
stub = qlgen_opts.ql_stub_output / "A.qll"
stub = qlgen_opts.ql_stub_output / "AImpl.qll"
render_manager.is_customized_stub.side_effect = lambda f: f == stub
assert stub not in generate([schema.Class('A')])
@@ -732,14 +760,16 @@ def test_property_description(generate_classes):
schema.SingleProperty("foo", "bar", description=description),
]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
tableparams=["this", "result"],
doc="foo of this my object",
description=description),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -748,12 +778,14 @@ def test_property_doc_override(generate_classes):
schema.Class("MyObject", properties=[
schema.SingleProperty("foo", "bar", doc="baz")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
tableparams=["this", "result"], doc="baz"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -763,7 +795,8 @@ def test_repeated_property_doc_override(generate_classes):
schema.RepeatedProperty("x", "int", doc="children of this"),
schema.RepeatedOptionalProperty("y", "int", doc="child of this")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="X", plural="Xes", type="int",
@@ -774,7 +807,8 @@ def test_repeated_property_doc_override(generate_classes):
tablename="my_object_ies", is_optional=True,
tableparams=["this", "index", "result"],
doc="child of this", doc_plural="children of this"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -785,13 +819,15 @@ def test_property_doc_abbreviations(generate_classes, abbr, expected):
schema.Class("Object", properties=[
schema.SingleProperty(f"foo_{abbr}_bar", "baz")]),
]) == {
"Object.qll": (a_ql_stub(name="Object"),
"Object.qll": (a_ql_class_public(name="Object"),
a_ql_stub(name="Object"),
a_ql_class(name="Object", final=True,
properties=[
ql.Property(singular=f"Foo{abbr.capitalize()}Bar", type="baz",
tablename="objects",
tableparams=["this", "result"], doc=expected_doc),
])),
],
imports=[stub_import_prefix + "Object"])),
}
@@ -802,13 +838,15 @@ def test_property_doc_abbreviations_ignored_if_within_word(generate_classes, abb
schema.Class("Object", properties=[
schema.SingleProperty(f"foo_{abbr}acadabra_bar", "baz")]),
]) == {
"Object.qll": (a_ql_stub(name="Object"),
"Object.qll": (a_ql_class_public(name="Object"),
a_ql_stub(name="Object"),
a_ql_class(name="Object", final=True,
properties=[
ql.Property(singular=f"Foo{abbr.capitalize()}acadabraBar", type="baz",
tablename="objects",
tableparams=["this", "result"], doc=expected_doc),
])),
],
imports=[stub_import_prefix + "Object"])),
}
@@ -818,7 +856,8 @@ def test_repeated_property_doc_override_with_format(generate_classes):
schema.RepeatedProperty("x", "int", doc="special {children} of this"),
schema.RepeatedOptionalProperty("y", "int", doc="special {child} of this")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="X", plural="Xes", type="int",
@@ -831,7 +870,8 @@ def test_repeated_property_doc_override_with_format(generate_classes):
tableparams=["this", "index", "result"],
doc="special child of this",
doc_plural="special children of this"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -841,7 +881,8 @@ def test_repeated_property_doc_override_with_multiple_formats(generate_classes):
schema.RepeatedProperty("x", "int", doc="{cat} or {dog}"),
schema.RepeatedOptionalProperty("y", "int", doc="{cats} or {dogs}")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="X", plural="Xes", type="int",
@@ -852,7 +893,8 @@ def test_repeated_property_doc_override_with_multiple_formats(generate_classes):
tablename="my_object_ies", is_optional=True,
tableparams=["this", "index", "result"],
doc="cat or dog", doc_plural="cats or dogs"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -861,12 +903,14 @@ def test_property_doc_override_with_format(generate_classes):
schema.Class("MyObject", properties=[
schema.SingleProperty("foo", "bar", doc="special {baz} of this")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
tableparams=["this", "result"], doc="special baz of this"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -876,12 +920,14 @@ def test_property_on_class_with_default_doc_name(generate_classes):
schema.SingleProperty("foo", "bar")],
default_doc_name="baz"),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
tableparams=["this", "result"], doc="foo of this baz"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -890,13 +936,13 @@ def test_stub_on_class_with_synth_from_class(generate_classes):
schema.Class("MyObject", synth=schema.SynthInfo(from_class="A"),
properties=[schema.SingleProperty("foo", "bar")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject", synth_accessors=[
"MyObject.qll": (a_ql_class_public(name="MyObject"), a_ql_stub(name="MyObject", synth_accessors=[
ql.SynthUnderlyingAccessor(argument="Entity", type="Raw::A", constructorparams=["result"]),
]),
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects", synth=True,
tableparams=["this", "result"], doc="foo of this my object"),
])),
], imports=[stub_import_prefix + "MyObject"])),
}
@@ -905,7 +951,7 @@ def test_stub_on_class_with_synth_on_arguments(generate_classes):
schema.Class("MyObject", synth=schema.SynthInfo(on_arguments={"base": "A", "index": "int", "label": "string"}),
properties=[schema.SingleProperty("foo", "bar")]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject", synth_accessors=[
"MyObject.qll": (a_ql_class_public(name="MyObject"), a_ql_stub(name="MyObject", synth_accessors=[
ql.SynthUnderlyingAccessor(argument="Base", type="Raw::A", constructorparams=["result", "_", "_"]),
ql.SynthUnderlyingAccessor(argument="Index", type="int", constructorparams=["_", "result", "_"]),
ql.SynthUnderlyingAccessor(argument="Label", type="string", constructorparams=["_", "_", "result"]),
@@ -913,7 +959,7 @@ def test_stub_on_class_with_synth_on_arguments(generate_classes):
a_ql_class(name="MyObject", final=True, properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects", synth=True,
tableparams=["this", "result"], doc="foo of this my object"),
])),
], imports=[stub_import_prefix + "MyObject"])),
}
@@ -922,13 +968,15 @@ def test_synth_property(generate_classes):
schema.Class("MyObject", properties=[
schema.SingleProperty("foo", "bar", synth=True)]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"),
"MyObject.qll": (a_ql_class_public(name="MyObject"),
a_ql_stub(name="MyObject"),
a_ql_class(name="MyObject", final=True,
properties=[
ql.Property(singular="Foo", type="bar", tablename="my_objects",
synth=True,
tableparams=["this", "result"], doc="foo of this my object"),
])),
],
imports=[stub_import_prefix + "MyObject"])),
}
@@ -936,7 +984,7 @@ def test_hideable_class(generate_classes):
assert generate_classes([
schema.Class("MyObject", hideable=True),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"), a_ql_class(name="MyObject", final=True, hideable=True)),
"MyObject.qll": (a_ql_class_public(name="MyObject"), a_ql_stub(name="MyObject"), a_ql_class(name="MyObject", final=True, hideable=True, imports=[stub_import_prefix + "MyObject"])),
}
@@ -947,9 +995,10 @@ def test_hideable_property(generate_classes):
schema.SingleProperty("x", "MyObject"),
]),
]) == {
"MyObject.qll": (a_ql_stub(name="MyObject"), a_ql_class(name="MyObject", final=True, hideable=True)),
"Other.qll": (a_ql_stub(name="Other"),
a_ql_class(name="Other", imports=[stub_import_prefix + "MyObject"],
"MyObject.qll": (a_ql_class_public(name="MyObject"), a_ql_stub(name="MyObject"), a_ql_class(name="MyObject", final=True, hideable=True, imports=[stub_import_prefix + "MyObject"])),
"Other.qll": (a_ql_class_public(name="Other", imports=[stub_import_prefix + "MyObject"]),
a_ql_stub(name="Other"),
a_ql_class(name="Other", imports=[stub_import_prefix + "MyObject", stub_import_prefix + "Other"],
final=True, properties=[
ql.Property(singular="X", type="MyObject", tablename="others",
type_is_hideable=True,