mirror of
https://github.com/github/codeql.git
synced 2025-12-17 09:13:20 +01:00
260 lines
6.4 KiB
Python
260 lines
6.4 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, Union, Optional
|
|
|
|
import inflection
|
|
|
|
|
|
@dataclass
|
|
class Param:
|
|
param: str
|
|
first: bool = False
|
|
|
|
|
|
@dataclass
|
|
class Property:
|
|
singular: str
|
|
type: Optional[str] = None
|
|
tablename: Optional[str] = None
|
|
tableparams: List[Param] = field(default_factory=list)
|
|
plural: Optional[str] = None
|
|
first: bool = False
|
|
is_optional: bool = False
|
|
is_predicate: bool = False
|
|
prev_child: Optional[str] = None
|
|
qltest_skip: bool = False
|
|
|
|
def __post_init__(self):
|
|
if 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)
|
|
|
|
@property
|
|
def is_child(self):
|
|
return self.prev_child is not None
|
|
|
|
|
|
@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
|
|
ipa: bool = False
|
|
|
|
def __post_init__(self):
|
|
self.bases = sorted(self.bases)
|
|
if self.properties:
|
|
self.properties[0].first = True
|
|
|
|
@property
|
|
def root(self) -> bool:
|
|
return not self.bases
|
|
|
|
@property
|
|
def path(self) -> pathlib.Path:
|
|
return self.dir / self.name
|
|
|
|
@property
|
|
def db_id(self):
|
|
return "@" + inflection.underscore(self.name)
|
|
|
|
@property
|
|
def has_children(self):
|
|
return any(p.is_child for p in self.properties)
|
|
|
|
|
|
@dataclass
|
|
class Stub:
|
|
template: ClassVar = 'ql_stub'
|
|
|
|
name: str
|
|
base_import: str
|
|
|
|
|
|
@dataclass
|
|
class DbClasses:
|
|
template: ClassVar = 'ql_db'
|
|
|
|
classes: List[Class] = field(default_factory=list)
|
|
|
|
|
|
@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: Optional[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'
|
|
|
|
|
|
class Synth:
|
|
@dataclass
|
|
class Class:
|
|
is_final: ClassVar = False
|
|
|
|
name: str
|
|
first: bool = False
|
|
|
|
@dataclass
|
|
class Param:
|
|
param: str
|
|
type: str
|
|
first: bool = False
|
|
|
|
@dataclass
|
|
class FinalClass(Class):
|
|
is_final: ClassVar = True
|
|
is_derived_ipa: ClassVar = False
|
|
is_fresh_ipa: ClassVar = False
|
|
is_db: ClassVar = False
|
|
|
|
params: List["Synth.Param"] = field(default_factory=list)
|
|
|
|
def __post_init__(self):
|
|
if self.params:
|
|
self.params[0].first = True
|
|
|
|
@property
|
|
def is_ipa(self):
|
|
return self.is_fresh_ipa or self.is_derived_ipa
|
|
|
|
@property
|
|
def has_params(self) -> bool:
|
|
return bool(self.params)
|
|
|
|
@dataclass
|
|
class FinalClassIpa(FinalClass):
|
|
pass
|
|
|
|
@dataclass
|
|
class FinalClassDerivedIpa(FinalClassIpa):
|
|
is_derived_ipa: ClassVar = True
|
|
|
|
@dataclass
|
|
class FinalClassFreshIpa(FinalClassIpa):
|
|
is_fresh_ipa: ClassVar = True
|
|
|
|
@dataclass
|
|
class FinalClassDb(FinalClass):
|
|
is_db: ClassVar = True
|
|
|
|
subtracted_ipa_types: List["Synth.Class"] = field(default_factory=list)
|
|
|
|
def subtract_type(self, type: str):
|
|
self.subtracted_ipa_types.append(Synth.Class(type, first=not self.subtracted_ipa_types))
|
|
|
|
@property
|
|
def has_subtracted_ipa_types(self) -> bool:
|
|
return bool(self.subtracted_ipa_types)
|
|
|
|
@property
|
|
def db_id(self) -> str:
|
|
return "@" + inflection.underscore(self.name)
|
|
|
|
@dataclass
|
|
class NonFinalClass(Class):
|
|
derived: List["Synth.Class"] = field(default_factory=list)
|
|
root: bool = False
|
|
|
|
def __post_init__(self):
|
|
self.derived = [Synth.Class(c) for c in self.derived]
|
|
if self.derived:
|
|
self.derived[0].first = True
|
|
|
|
@dataclass
|
|
class Types:
|
|
template: ClassVar = "ql_ipa_types"
|
|
|
|
root: str
|
|
final_classes: List["Synth.FinalClass"] = field(default_factory=list)
|
|
non_final_classes: List["Synth.NonFinalClass"] = field(default_factory=list)
|
|
|
|
def __post_init__(self):
|
|
if self.final_classes:
|
|
self.final_classes[0].first = True
|
|
|
|
@dataclass
|
|
class ConstructorStub:
|
|
template: ClassVar = "ql_ipa_constructor_stub"
|
|
|
|
cls: "Synth.FinalClass"
|