2
2
QL code generation
3
3
4
4
`generate(opts, renderer)` will generate in the library directory:
5
- * generated/Raw.qll with thin class wrappers around DB types
6
- * generated/Synth.qll with the base algebraic datatypes for AST entities
7
- * generated/<group>/<Class>.qll with generated properties for each class
8
- * if not already modified, a elements/<group>/<Class>.qll stub to customize the above classes
9
- * elements.qll importing all the above stubs
10
- * if not already modified, a elements/<group>/<Class>Constructor.qll stub to customize the algebraic datatype
5
+ * `generated/Raw.qll` with thin class wrappers around DB types
6
+ * `generated/Synth.qll` with the base algebraic datatypes for AST entities
7
+ * `generated/<group>/<Class>.qll` with generated properties for each class
8
+ * if not already modified, an `elements/<group>/<Class>Impl.qll` stub to customize the above classes
9
+ * `elements/<group>/<Class>.qll` that wraps the internal `<Class>Impl.qll` file in a public `final` class.
10
+ * `elements.qll` importing all the above public classes
11
+ * if not already modified, an `elements/<group>/<Class>Constructor.qll` stub to customize the algebraic datatype
11
12
characteristic predicate
12
- * generated/SynthConstructors.qll importing all the above constructor stubs
13
- * generated/PureSynthConstructors.qll importing constructor stubs for pure synthesized types (that is, not
13
+ * ` generated/SynthConstructors.qll` importing all the above constructor stubs
14
+ * ` generated/PureSynthConstructors.qll` importing constructor stubs for pure synthesized types (that is, not
14
15
corresponding to raw types)
15
16
Moreover in the test directory for each <Class> in <group> it will generate beneath the
16
- extractor-tests/generated/<group>/<Class> directory either
17
+ ` extractor-tests/generated/<group>/<Class>` directory either
17
18
* a `MISSING_SOURCE.txt` explanation file if no source is present, or
18
19
* one `<Class>.ql` test query for all single properties and on `<Class>_<property>.ql` test query for each optional or
19
20
repeated property
@@ -164,6 +165,7 @@ def get_ql_class(cls: schema.Class, lookup: typing.Dict[str, schema.Class]) -> q
164
165
return ql .Class (
165
166
name = cls .name ,
166
167
bases = cls .bases ,
168
+ bases_impl = [base + "Impl::" + base for base in cls .bases ],
167
169
final = not cls .derived ,
168
170
properties = properties ,
169
171
dir = pathlib .Path (cls .group or "" ),
@@ -210,15 +212,17 @@ def get_import(file: pathlib.Path, root_dir: pathlib.Path):
210
212
return str (stem ).replace ("/" , "." )
211
213
212
214
213
- def get_types_used_by (cls : ql .Class ) -> typing .Iterable [str ]:
215
+ def get_types_used_by (cls : ql .Class , is_impl : bool ) -> typing .Iterable [str ]:
214
216
for b in cls .bases :
215
- yield b .base
217
+ yield b .base + "Impl" if is_impl else b . base
216
218
for p in cls .properties :
217
219
yield p .type
220
+ if cls .root :
221
+ yield cls .name # used in `getResolveStep` and `resolve`
218
222
219
223
220
- def get_classes_used_by (cls : ql .Class ) -> typing .List [str ]:
221
- return sorted (set (t for t in get_types_used_by (cls ) if t [0 ].isupper () and t != cls .name ))
224
+ def get_classes_used_by (cls : ql .Class , is_impl : bool ) -> typing .List [str ]:
225
+ return sorted (set (t for t in get_types_used_by (cls , is_impl ) if t [0 ].isupper () and ( is_impl or t != cls .name ) ))
222
226
223
227
224
228
def format (codeql , files ):
@@ -239,6 +243,10 @@ def _get_path(cls: schema.Class) -> pathlib.Path:
239
243
return pathlib .Path (cls .group or "" , cls .name ).with_suffix (".qll" )
240
244
241
245
246
+ def _get_path_impl (cls : schema .Class ) -> pathlib .Path :
247
+ return pathlib .Path (cls .group or "" , cls .name + "Impl" ).with_suffix (".qll" )
248
+
249
+
242
250
def _get_all_properties (cls : schema .Class , lookup : typing .Dict [str , schema .Class ],
243
251
already_seen : typing .Optional [typing .Set [int ]] = None ) -> \
244
252
typing .Iterable [typing .Tuple [schema .Class , schema .Property ]]:
@@ -315,11 +323,13 @@ def _get_stub(cls: schema.Class, base_import: str, generated_import_prefix: str)
315
323
else :
316
324
accessors = []
317
325
return ql .Stub (name = cls .name , base_import = base_import , import_prefix = generated_import_prefix ,
318
- doc = cls .doc , synth_accessors = accessors ,
319
- internal = "ql_internal" in cls .pragmas )
326
+ doc = cls .doc , synth_accessors = accessors )
327
+
328
+ def _get_class_public (cls : schema .Class ) -> ql .ClassPublic :
329
+ return ql .ClassPublic (name = cls .name , doc = cls .doc , internal = "ql_internal" in cls .pragmas )
320
330
321
331
322
- _stub_qldoc_header = "// the following QLdoc is generated: if you need to edit it, do it in the schema file\n "
332
+ _stub_qldoc_header = "// the following QLdoc is generated: if you need to edit it, do it in the schema file\n "
323
333
324
334
_class_qldoc_re = re .compile (
325
335
rf"(?P<qldoc>(?:{ re .escape (_stub_qldoc_header )} )?/\*\*.*?\*/\s*|^\s*)(?:class\s+(?P<class>\w+))?" ,
@@ -330,13 +340,13 @@ def _patch_class_qldoc(cls: str, qldoc: str, stub_file: pathlib.Path):
330
340
""" Replace or insert `qldoc` as the QLdoc of class `cls` in `stub_file` """
331
341
if not qldoc or not stub_file .exists ():
332
342
return
333
- qldoc = "\n " .join (l .rstrip () for l in qldoc .splitlines ())
343
+ qldoc = "\n " .join (l .rstrip () for l in qldoc .splitlines ())
334
344
with open (stub_file ) as input :
335
345
contents = input .read ()
336
346
for match in _class_qldoc_re .finditer (contents ):
337
347
if match ["class" ] == cls :
338
348
qldoc_start , qldoc_end = match .span ("qldoc" )
339
- contents = f"{ contents [:qldoc_start ]} { _stub_qldoc_header } { qldoc } \n { contents [qldoc_end :]} "
349
+ contents = f"{ contents [:qldoc_start ]} { _stub_qldoc_header } { qldoc } \n { contents [qldoc_end :]} "
340
350
tmp = stub_file .with_suffix (f"{ stub_file .suffix } .bkp" )
341
351
with open (tmp , "w" ) as out :
342
352
out .write (contents )
@@ -370,6 +380,8 @@ def generate(opts, renderer):
370
380
raise RootElementHasChildren (root )
371
381
372
382
imports = {}
383
+ imports_impl = {}
384
+ classes_used_by = {}
373
385
generated_import_prefix = get_import (out , opts .root_dir )
374
386
registry = opts .generated_registry or pathlib .Path (
375
387
os .path .commonpath ((out , stub_out , test_out )), ".generated.list" )
@@ -382,24 +394,33 @@ def generate(opts, renderer):
382
394
383
395
classes_by_dir_and_name = sorted (classes .values (), key = lambda cls : (cls .dir , cls .name ))
384
396
for c in classes_by_dir_and_name :
385
- imports [c .name ] = get_import (stub_out / c .path , opts .root_dir )
397
+ path = get_import (stub_out / c .path , opts .root_dir )
398
+ imports [c .name ] = path
399
+ imports_impl [c .name + "Impl" ] = path + "Impl"
386
400
387
401
for c in classes .values ():
388
402
qll = out / c .path .with_suffix (".qll" )
389
- c .imports = [imports [t ] for t in get_classes_used_by (c )]
403
+ c .imports = [imports [t ] if t in imports else imports_impl [t ]+ "::Impl as " + t for t in get_classes_used_by (c , is_impl = True )]
404
+ classes_used_by [c .name ] = get_classes_used_by (c , is_impl = False )
390
405
c .import_prefix = generated_import_prefix
391
406
renderer .render (c , qll )
392
407
393
408
for c in data .classes .values ():
394
409
path = _get_path (c )
395
- stub_file = stub_out / path
410
+ path_impl = _get_path_impl (c )
411
+ stub_file = stub_out / path_impl
396
412
base_import = get_import (out / path , opts .root_dir )
397
413
stub = _get_stub (c , base_import , generated_import_prefix )
414
+
398
415
if not renderer .is_customized_stub (stub_file ):
399
416
renderer .render (stub , stub_file )
400
417
else :
401
418
qldoc = renderer .render_str (stub , template = 'ql_stub_class_qldoc' )
402
419
_patch_class_qldoc (c .name , qldoc , stub_file )
420
+ class_public = _get_class_public (c )
421
+ class_public_file = stub_out / path
422
+ class_public .imports = [imports [t ] for t in classes_used_by [c .name ]]
423
+ renderer .render (class_public , class_public_file )
403
424
404
425
# for example path/to/elements -> path/to/elements.qll
405
426
renderer .render (ql .ImportList ([i for name , i in imports .items () if not classes [name ].internal ]),
0 commit comments