Clean up ast builder code

This commit is contained in:
Andrew Eisenberg
2020-07-22 11:43:22 -07:00
parent 65b5b68df6
commit 0045891f9d
5 changed files with 96 additions and 57 deletions

View File

@@ -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()] : []

View File

@@ -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;

View File

@@ -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
});
}
}
}

View File

@@ -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);

View File

@@ -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 })