mirror of
https://github.com/github/codeql.git
synced 2025-12-17 09:13:20 +01:00
This collapses all generated test QL sources into a single one per directory, using query predicates to run the different tests. This should improve the time required to run generated tests.
363 lines
9.2 KiB
Python
363 lines
9.2 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
|
|
is_unordered: bool = False
|
|
prev_child: Optional[str] = None
|
|
qltest_skip: bool = False
|
|
description: List[str] = field(default_factory=list)
|
|
doc: Optional[str] = None
|
|
doc_plural: Optional[str] = None
|
|
synth: bool = False
|
|
type_is_hideable: bool = False
|
|
type_is_codegen_class: bool = False
|
|
type_is_self: bool = False
|
|
internal: bool = False
|
|
cfg: 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):
|
|
if self.is_predicate:
|
|
return self.singular
|
|
if self.is_unordered:
|
|
return self.indefinite_getter
|
|
return f"get{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 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
|
|
|
|
@property
|
|
def is_indexed(self) -> bool:
|
|
return self.is_repeated and not self.is_unordered
|
|
|
|
@property
|
|
def type_alias(self) -> Optional[str]:
|
|
return self.type + "Alias" if self.type_is_self else self.type
|
|
|
|
|
|
@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)
|
|
bases_impl: 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)
|
|
import_prefix: Optional[str] = None
|
|
internal: bool = False
|
|
doc: List[str] = field(default_factory=list)
|
|
hideable: bool = False
|
|
cfg: bool = False
|
|
|
|
def __post_init__(self):
|
|
def get_bases(bases):
|
|
return [
|
|
Base(str(b), str(prev))
|
|
for b, prev in zip(bases, itertools.chain([""], bases))
|
|
]
|
|
|
|
self.bases = get_bases(self.bases)
|
|
self.bases_impl = get_bases(self.bases_impl)
|
|
if self.properties:
|
|
self.properties[0].first = True
|
|
for prop in self.properties:
|
|
if prop.type == self.name:
|
|
prop.type_is_self = True
|
|
|
|
@property
|
|
def root(self) -> bool:
|
|
return not self.bases
|
|
|
|
@property
|
|
def needs_self_alias(self) -> bool:
|
|
return self.root or any(p.type_is_self for p in self.properties)
|
|
|
|
@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 SynthUnderlyingAccessor:
|
|
argument: str
|
|
type: str
|
|
constructorparams: List[Param]
|
|
|
|
def __post_init__(self):
|
|
if self.constructorparams:
|
|
self.constructorparams = [Param(x) for x in self.constructorparams]
|
|
self.constructorparams[0].first = True
|
|
|
|
|
|
@dataclass
|
|
class Stub:
|
|
template: ClassVar = "ql_stub"
|
|
|
|
name: str
|
|
base_import: str
|
|
import_prefix: str
|
|
synth_accessors: List[SynthUnderlyingAccessor] = field(default_factory=list)
|
|
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
|
|
|
|
|
|
@dataclass
|
|
class DbClasses:
|
|
template: ClassVar = "ql_db"
|
|
|
|
classes: List[Class] = field(default_factory=list)
|
|
imports: List[str] = 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)
|
|
imports: List[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class PropertyForTest:
|
|
getter: str
|
|
is_total: bool = True
|
|
type: Optional[str] = None
|
|
is_indexed: bool = False
|
|
|
|
|
|
@dataclass
|
|
class TesterBase:
|
|
class_name: str
|
|
elements_module: str
|
|
|
|
|
|
@dataclass
|
|
class ClassTester(TesterBase):
|
|
template: ClassVar = "ql_test_class"
|
|
|
|
properties: List[PropertyForTest] = field(default_factory=list)
|
|
show_ql_class: bool = False
|
|
|
|
|
|
@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_synth: ClassVar = False
|
|
is_fresh_synth: 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_synth(self):
|
|
return self.is_fresh_synth or self.is_derived_synth
|
|
|
|
@property
|
|
def has_params(self) -> bool:
|
|
return bool(self.params)
|
|
|
|
@dataclass
|
|
class FinalClassSynth(FinalClass):
|
|
pass
|
|
|
|
@dataclass
|
|
class FinalClassDerivedSynth(FinalClassSynth):
|
|
is_derived_synth: ClassVar = True
|
|
|
|
@dataclass
|
|
class FinalClassFreshSynth(FinalClassSynth):
|
|
is_fresh_synth: ClassVar = True
|
|
|
|
@dataclass
|
|
class FinalClassDb(FinalClass):
|
|
is_db: ClassVar = True
|
|
|
|
subtracted_synth_types: List["Synth.Class"] = field(default_factory=list)
|
|
|
|
def subtract_type(self, type: str):
|
|
self.subtracted_synth_types.append(
|
|
Synth.Class(type, first=not self.subtracted_synth_types)
|
|
)
|
|
|
|
@property
|
|
def has_subtracted_synth_types(self) -> bool:
|
|
return bool(self.subtracted_synth_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_synth_types"
|
|
|
|
root: str
|
|
import_prefix: 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_synth_constructor_stub"
|
|
|
|
cls: "Synth.FinalClass"
|
|
import_prefix: str
|
|
|
|
|
|
@dataclass
|
|
class CfgClass:
|
|
name: str
|
|
bases: List[Base] = field(default_factory=list)
|
|
properties: List[Property] = field(default_factory=list)
|
|
doc: List[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class CfgClasses:
|
|
template: ClassVar = "ql_cfg_nodes"
|
|
include_file_import: Optional[str] = None
|
|
classes: List[CfgClass] = field(default_factory=list)
|