зеркало из https://github.com/github/codeql.git
336 строки
8.4 KiB
Python
336 строки
8.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
|
|
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
|
|
internal: 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 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
|
|
|
|
@property
|
|
def is_indexed(self) -> bool:
|
|
return self.is_repeated and not self.is_unordered
|
|
|
|
|
|
@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
|
|
|
|
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
|
|
|
|
@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 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)
|
|
|
|
|
|
@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 PropertyTester(TesterBase):
|
|
template: ClassVar = 'ql_test_property'
|
|
|
|
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_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
|