Files
codeql/swift/codegen/lib/ql.py
2022-06-28 12:01:36 +02:00

154 lines
3.8 KiB
Python

"""
QL files generation
`generate(opts, renderer)` will generate QL classes and manage stub files out of a `yml` schema file.
Each class (for example, `Foo`) in the schema triggers:
* generation of a `FooBase` class implementation translating all properties into appropriate getters
* if not created or already customized, generation of a stub file which defines `Foo` as extending `FooBase`. This can
be used to add hand-written code to `Foo`, which requires removal of the `// generated` header comment in that file.
All generated base classes actually import these customizations when referencing other classes.
Generated files that do not correspond any more to any class in the schema are deleted. Customized stubs are however
left behind and must be dealt with by hand.
"""
import pathlib
from dataclasses import dataclass, field
from typing import List, ClassVar
import inflection
@dataclass
class Param:
param: str
first: bool = False
@dataclass
class Property:
singular: str
type: str = None
tablename: str = None
tableparams: List[Param] = field(default_factory=list)
plural: str = None
first: bool = False
local_var: str = "x"
is_optional: bool = False
is_predicate: bool = False
is_child: bool = False
qltest_skip: bool = False
def __post_init__(self):
if self.tableparams:
if self.type_is_class:
self.tableparams = [x if x != "result" else self.local_var for x in self.tableparams]
self.tableparams = [Param(x) for x in self.tableparams]
self.tableparams[0].first = True
@property
def getter(self):
return f"get{self.singular}" if not self.is_predicate else self.singular
@property
def indefinite_getter(self):
if self.plural:
article = "An" if self.singular[0] in "AEIO" else "A"
return f"get{article}{self.singular}"
@property
def type_is_class(self):
return bool(self.type) and self.type[0].isupper()
@property
def is_repeated(self):
return bool(self.plural)
@property
def is_single(self):
return not (self.is_optional or self.is_repeated or self.is_predicate)
@dataclass
class Class:
template: ClassVar = 'ql_class'
name: str
bases: List[str] = field(default_factory=list)
final: bool = False
properties: List[Property] = field(default_factory=list)
dir: pathlib.Path = pathlib.Path()
imports: List[str] = field(default_factory=list)
qltest_skip: bool = False
qltest_collapse_hierarchy: bool = False
qltest_uncollapse_hierarchy: bool = False
def __post_init__(self):
self.bases = sorted(self.bases)
if self.properties:
self.properties[0].first = True
@property
def db_id(self):
return "@" + inflection.underscore(self.name)
@property
def root(self):
return not self.bases
@property
def path(self):
return self.dir / self.name
@dataclass
class Stub:
template: ClassVar = 'ql_stub'
name: str
base_import: str
@dataclass
class ImportList:
template: ClassVar = 'ql_imports'
imports: List[str] = field(default_factory=list)
@dataclass
class GetParentImplementation:
template: ClassVar = 'ql_parent'
classes: List[Class] = field(default_factory=list)
@dataclass
class PropertyForTest:
getter: str
type: str = None
is_single: bool = False
is_predicate: bool = False
is_repeated: bool = False
@dataclass
class ClassTester:
template: ClassVar = 'ql_test_class'
class_name: str
properties: List[PropertyForTest] = field(default_factory=list)
@dataclass
class PropertyTester:
template: ClassVar = 'ql_test_property'
class_name: str
property: PropertyForTest
@dataclass
class MissingTestInstructions:
template: ClassVar = 'ql_test_missing'