Source: extract/CallPropertyExtractor.js

import {Dsl} from '../config/Dsl.js';
import {CallProperties} from './CallProperties.js';
import {Logger} from '../util/Logger.js';

/**
 * Extractor for retrieving and caching the semantic properties of Calls.
 * @implements {ExtractorInterface<CallProperties>}
 */
export class CallPropertyExtractor {
  /**
   * @inheritDoc
   * @type {Map<Node,CallProperties>}
   * @protected
   */
  _memo;

  /**
   * Extract the call properties for a Call and cache them.
   * @param {Node} call The call node.
   * @protected
   */
  _extract(call) {
    if (!call.isCall()) {
      Logger.error('Cannot compute call properties for non-call node', this);
    }
    const endpoint = call.attributes.get(Dsl.CALL_PROPERTIES.ENDPOINT.label);

    let method;
    let label;
    let argKeys;
    let argVals;
    const parameters =
        call
            .children
            .find((property) =>
              property.label === Dsl.CALL_PROPERTIES.PARAMETERS.label);
    if (parameters != null) {
      method =
          parameters
              .children
              .find((property) =>
                property.label === Dsl.CALL_PROPERTIES.METHOD.label)?.text;
      label =
          parameters
              .children
              .find((property) =>
                property.label === Dsl.CALL_PROPERTIES.LABEL.label)
              ?.text;
      const argsParent = parameters
          .children
          .find((property) =>
            property.label === Dsl.CALL_PROPERTIES.ARGUMENTS.label);
      argKeys = argsParent
          ?.children
          .map((arg) => arg.label);
      argVals = argsParent
          ?.children
          .map((arg) => arg.text);
      if (argKeys == null) {
        argKeys = [];
        argVals = [];
      }
    }
    const code =
        call
            .children
            .find((property) =>
              property.label === Dsl.CALL_PROPERTIES.CODE.label)
            ?.children
            // sort code nodes (e.g., finalize, prepare, rescue, update)
            // by label to guarantee String equality on equal code
            .sort((a, b) => a.label.localeCompare(b.label))
            .map((codeNode) => codeNode.text)
            .join('');
    this._memo.set(
        call,
        new CallProperties(endpoint, method, label, argKeys, argVals, code),
    );
  }

  /**
   * Get the properties of a call. If they are not cached,
   * calculate and cache it first.
   * @param {Node} call
   * @return {CallProperties}
   */
  get(call) {
    if (!this._memo.has(call)) {
      this._extract(call);
    }
    return this._memo.get(call);
  }

  /**
   * Create a new Extractor instance
   */
  constructor() {
    this._memo = new Map();
  }
}