File size: 1,853 Bytes
aa89cb5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import * as parser from './query-parser.generated';

type CallNode = {
  type: 'Call';
  method: string;
  arguments: ValueNode[];
};

type CallChainNode = {
  type: 'CallChain';
  target: string;
  calls: CallNode[];
};

type ValueNode = CallChainNode | string | number | boolean | null | ValueNode[];

const FORBIDDEN_PROPERTY_NAMES = new Set(['__proto__', 'prototype', 'constructor']);

function executeCallChain(node: CallChainNode, entryPoint: object): unknown {
  if (node.target !== 'Q') {
    throw new Error(`Unsupported query entry point ${node.target}; expected Q`);
  }
  let receiver: unknown = entryPoint;
  for (const call of node.calls) {
    if (FORBIDDEN_PROPERTY_NAMES.has(call.method)) {
      throw new Error(`Method ${call.method} is not allowed in a query expression`);
    }
    if ((typeof receiver !== 'object' && typeof receiver !== 'function') || receiver === null) {
      throw new Error(`Cannot call ${call.method} on a non-query value`);
    }
    const method = (receiver as Record<string, unknown>)[call.method];
    if (typeof method !== 'function') {
      throw new Error(`Method ${call.method} is not available on the generated query API`);
    }
    const args = call.arguments.map(argument =>
      typeof argument === 'object' && argument !== null && !Array.isArray(argument)
        ? executeCallChain(argument as CallChainNode, entryPoint)
        : argument,
    );
    receiver = method.apply(receiver, args);
  }
  return receiver;
}

export class QueryParser {
  // The concrete return type is supplied by the generated entry point at runtime.
  // Keep `any` for source compatibility with existing generated Q facades.
  static parse(querySource: string, entryPoint: object): any {
    const ast = parser.parse(querySource, undefined) as CallChainNode;
    return executeCallChain(ast, entryPoint);
  }
}