mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
In those kinds of tests the results may have different final classes that are not necessarily visible (or tested) solely through the string representation. For better testing and reading of expected results, `getQlPrimaryClasses` is added in these cases.
275 lines
6.8 KiB
Python
275 lines
6.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
|
|
import itertools
|
|
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 Base:
|
|
base: str
|
|
prev: str = ""
|
|
|
|
def __str__(self):
|
|
return self.base
|
|
|
|
|
|
@dataclass
|
|
class Class:
|
|
template: ClassVar = 'ql_class'
|
|
|
|
name: str
|
|
bases: List[Base] = 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 = [Base(str(b), str(prev)) for b, prev in zip(self.bases, itertools.chain([""], 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) -> str:
|
|
return "@" + inflection.underscore(self.name)
|
|
|
|
@property
|
|
def has_children(self) -> bool:
|
|
return any(p.is_child for p in self.properties)
|
|
|
|
@property
|
|
def last_base(self) -> str:
|
|
return self.bases[-1].base if self.bases else ""
|
|
|
|
|
|
@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)
|
|
show_ql_class: bool = False
|
|
|
|
|
|
@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"
|