Clean up ast builder code
This commit is contained in:
@@ -93,6 +93,18 @@ export interface TestCompleted {
|
||||
expected: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional arguments for the `bqrsDecode` function
|
||||
*/
|
||||
interface BqrsDecodeOptions {
|
||||
/** How many results to get. */
|
||||
pageSize?: number;
|
||||
/** The 0-based index of the first result to get. */
|
||||
offset?: number;
|
||||
/** The entity names to retrieve from the bqrs file. Default is url, string */
|
||||
entities?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This class manages a cli server started by `codeql execute cli-server` to
|
||||
* run commands without the overhead of starting a new java
|
||||
@@ -494,12 +506,16 @@ export class CodeQLCliServer implements Disposable {
|
||||
* Gets the results from a bqrs.
|
||||
* @param bqrsPath The path to the bqrs.
|
||||
* @param resultSet The result set to get.
|
||||
* @param pageSize How many results to get.
|
||||
* @param offset The 0-based index of the first result to get.
|
||||
* @param options Optional BqrsDecodeOptions arguments
|
||||
*/
|
||||
async bqrsDecode(bqrsPath: string, resultSet: string, pageSize?: number, offset?: number): Promise<DecodedBqrsChunk> {
|
||||
async bqrsDecode(
|
||||
bqrsPath: string,
|
||||
resultSet: string,
|
||||
{ pageSize, offset, entities = ['url', 'string'] }: BqrsDecodeOptions = {}
|
||||
): Promise<DecodedBqrsChunk> {
|
||||
|
||||
const subcommandArgs = [
|
||||
'--entities=id,url,string',
|
||||
`--entities=${entities.join(',')}`,
|
||||
'--result-set', resultSet,
|
||||
].concat(
|
||||
pageSize ? ['--rows', pageSize.toString()] : []
|
||||
|
||||
@@ -8,7 +8,6 @@ import { AstItem, RootAstItem } from '../astViewer';
|
||||
* A class that wraps a tree of QL results from a query that
|
||||
* has an @kind of graph
|
||||
*/
|
||||
// RENAME to ASTParser / ASTCreator
|
||||
export default class AstBuilder {
|
||||
|
||||
private roots: RootAstItem[] | undefined;
|
||||
@@ -30,10 +29,11 @@ export default class AstBuilder {
|
||||
}
|
||||
|
||||
private async parseRoots(): Promise<RootAstItem[]> {
|
||||
const options = { entities: ['id', 'url', 'string'] };
|
||||
const [nodeTuples, edgeTuples, graphProperties] = await Promise.all([
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'nodes'),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'edges'),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'graphProperties'),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'nodes', options),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'edges', options),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'graphProperties', options),
|
||||
]);
|
||||
|
||||
if (!this.isValidGraph(graphProperties)) {
|
||||
@@ -48,61 +48,75 @@ export default class AstBuilder {
|
||||
|
||||
// Build up the parent-child relationships
|
||||
edgeTuples.tuples.forEach(tuple => {
|
||||
const from = tuple[0] as EntityValue;
|
||||
const to = tuple[1] as EntityValue;
|
||||
const toId = to.id!;
|
||||
const fromId = from.id!;
|
||||
const [source, target, tupleType, orderValue] = tuple as [EntityValue, EntityValue, string, string];
|
||||
const sourceId = source.id!;
|
||||
const targetId = target.id!;
|
||||
|
||||
if (tuple[2] === 'semmle.order') {
|
||||
astOrder.set(toId, Number(tuple[3]));
|
||||
} else if (tuple[2] === 'semmle.label') {
|
||||
childToParent.set(toId, fromId);
|
||||
let children = parentToChildren.get(fromId);
|
||||
if (!children) {
|
||||
parentToChildren.set(fromId, children = []);
|
||||
switch (tupleType) {
|
||||
case 'semmle.order':
|
||||
astOrder.set(targetId, Number(orderValue));
|
||||
break;
|
||||
|
||||
case 'semmle.label': {
|
||||
childToParent.set(targetId, sourceId);
|
||||
let children = parentToChildren.get(sourceId);
|
||||
if (!children) {
|
||||
parentToChildren.set(sourceId, children = []);
|
||||
}
|
||||
children.push(targetId);
|
||||
break;
|
||||
}
|
||||
children.push(toId);
|
||||
|
||||
default:
|
||||
// ignore other tupleTypes since they are not needed by the ast viewer
|
||||
}
|
||||
});
|
||||
|
||||
// populate parents and children
|
||||
nodeTuples.tuples.forEach(tuple => {
|
||||
const entity = tuple[0] as EntityValue;
|
||||
const [entity, tupleType, orderValue] = tuple as [EntityValue, string, string];
|
||||
const id = entity.id!;
|
||||
|
||||
if (tuple[1] === 'semmle.order') {
|
||||
astOrder.set(id, Number(tuple[2]));
|
||||
switch (tupleType) {
|
||||
case 'semmle.order':
|
||||
astOrder.set(id, Number(orderValue));
|
||||
break;
|
||||
|
||||
} else if (tuple[1] === 'semmle.label') {
|
||||
const item = {
|
||||
id,
|
||||
label: entity.label,
|
||||
location: entity.url,
|
||||
children: [] as AstItem[],
|
||||
order: Number.MAX_SAFE_INTEGER
|
||||
};
|
||||
case 'semmle.label': {
|
||||
const item = {
|
||||
id,
|
||||
label: entity.label,
|
||||
location: entity.url,
|
||||
children: [] as AstItem[],
|
||||
order: Number.MAX_SAFE_INTEGER
|
||||
};
|
||||
|
||||
idToItem.set(id, item as RootAstItem);
|
||||
const parent = idToItem.get(childToParent.get(id) || -1);
|
||||
idToItem.set(id, item as RootAstItem);
|
||||
const parent = idToItem.get(childToParent.get(id) || -1);
|
||||
|
||||
if (parent) {
|
||||
const astItem = item as AstItem;
|
||||
astItem.parent = parent;
|
||||
parent.children.push(astItem);
|
||||
}
|
||||
const children = parentToChildren.get(id) || [];
|
||||
children.forEach(childId => {
|
||||
const child = idToItem.get(childId) as AstItem | undefined;
|
||||
if (child) {
|
||||
child.parent = item;
|
||||
item.children.push(child);
|
||||
if (parent) {
|
||||
const astItem = item as AstItem;
|
||||
astItem.parent = parent;
|
||||
parent.children.push(astItem);
|
||||
}
|
||||
});
|
||||
const children = parentToChildren.get(id) || [];
|
||||
children.forEach(childId => {
|
||||
const child = idToItem.get(childId) as AstItem | undefined;
|
||||
if (child) {
|
||||
child.parent = item;
|
||||
item.children.push(child);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ignore other tupleTypes since they are not needed by the ast viewer
|
||||
}
|
||||
});
|
||||
|
||||
// find the roots and add the order
|
||||
for(const [, item] of idToItem) {
|
||||
for (const [, item] of idToItem) {
|
||||
item.order = astOrder.has(item.id)
|
||||
? astOrder.get(item.id)!
|
||||
: Number.MAX_SAFE_INTEGER;
|
||||
|
||||
@@ -257,12 +257,16 @@ async function getLinksFromResults(
|
||||
// TODO: Page this
|
||||
const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME);
|
||||
for (const tuple of allTuples.tuples) {
|
||||
const src = tuple[0] as EntityValue;
|
||||
const dest = tuple[1] as EntityValue;
|
||||
const [src, dest] = tuple as [EntityValue, EntityValue];
|
||||
const srcFile = src.url && fileRangeFromURI(src.url, db);
|
||||
const destFile = dest.url && fileRangeFromURI(dest.url, db);
|
||||
if (srcFile && destFile && filter(srcFile.uri.toString(), destFile.uri.toString())) {
|
||||
localLinks.push({ targetRange: destFile.range, targetUri: destFile.uri, originSelectionRange: srcFile.range, originUri: srcFile.uri });
|
||||
localLinks.push({
|
||||
targetRange: destFile.range,
|
||||
targetUri: destFile.uri,
|
||||
originSelectionRange: srcFile.range,
|
||||
originUri: srcFile.uri
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,8 +348,10 @@ export class InterfaceManager extends DisposableObject {
|
||||
const chunk = await this.cliServer.bqrsDecode(
|
||||
results.query.resultsPaths.resultsPath,
|
||||
schema.name,
|
||||
RAW_RESULTS_PAGE_SIZE,
|
||||
schema.pagination?.offsets[0]
|
||||
{
|
||||
offset: schema.pagination?.offsets[0],
|
||||
pageSize: RAW_RESULTS_PAGE_SIZE
|
||||
}
|
||||
);
|
||||
const adaptedSchema = adaptSchema(schema);
|
||||
const resultSet = adaptBqrs(adaptedSchema, chunk);
|
||||
@@ -446,8 +448,10 @@ export class InterfaceManager extends DisposableObject {
|
||||
const chunk = await this.cliServer.bqrsDecode(
|
||||
results.query.resultsPaths.resultsPath,
|
||||
schema.name,
|
||||
RAW_RESULTS_PAGE_SIZE,
|
||||
schema.pagination?.offsets[pageNumber]
|
||||
{
|
||||
offset: schema.pagination?.offsets[pageNumber],
|
||||
pageSize: RAW_RESULTS_PAGE_SIZE
|
||||
}
|
||||
);
|
||||
const adaptedSchema = adaptSchema(schema);
|
||||
const resultSet = adaptBqrs(adaptedSchema, chunk);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as fs from 'fs-extra';
|
||||
// import * as sinonChai from 'sinon-chai';
|
||||
import * as chai from 'chai';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import AstBuilder from '../../../contextual/astBuilder';
|
||||
import { QueryWithResults } from '../../../run-queries';
|
||||
import { CodeQLCliServer } from '../../../cli';
|
||||
@@ -55,9 +55,10 @@ describe('AstBuilder', () => {
|
||||
const astBuilder = createAstBuilder();
|
||||
const roots = await astBuilder.getRoots();
|
||||
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'nodes');
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'edges');
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'graphProperties');
|
||||
const options = { entities: ['id', 'url', 'string'] };
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'nodes', options);
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'edges', options);
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'graphProperties', options);
|
||||
|
||||
expect(roots.map(
|
||||
r => ({ ...r, children: undefined })
|
||||
|
||||
Reference in New Issue
Block a user