files
   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| 
  54| 
  55| 
  56| 
  57| 
  58| 
  59| 
  60| 
  61| 
  62| 
  63| 
  64| 
  65| 
  66| 
  67| 
  68| 
  69| 
  70| 
  71| 
  72| 
  73| 
  74| 
  75| 
  76| 
  77| 
  78| 
  79| 
  80| 
  81| 
  82| 
  83| 
  84| 
  85| 
  86| 
  87| 
  88| 
  89| 
  90| 
  91| 
  92| 
  93| 
  94| 
  95| 
  96| 
  97| 
  98| 
  99| 
 100| 
 101| 
 102| 
 103| 
 104| 
 105| 
 106| 
 107| 
 108| 
 109| 
 110| 
 111| 
 112| 
 113| 
 114| 
 115| 
 116| 
 117| 
 118| 
 119| 
 120| 
 121| 
 122| 
 123| 
 124| 
 125| 
 126| 
 127| 
 128| 
 129| 
 130| 
 131| 
 132| 
 133| 
 134| 
 135| 
 136| 
 137| 
 138| 
 139| 
 140| 
 141| 
 142| 
 143| 
 144| 
 145| 
 146| 
 147| 
 148| 
 149| 
 150| 

    
import { readFile } from "fs/promises";
import { MetaData, Vertex, Edge, Id, Document, next } from "lsif-protocol";

export type Element = Vertex | Edge;

type SafeMap<A, B> = {
  get: (x: A) => B,
  have: (x: A) => boolean,
};

export type Lsif = {
  items: Element[];
  item: SafeMap<Id, Element>;
  inV: SafeMap<Id, Element[]>;
  outV: SafeMap<Id, Element[]>;
  documents: Document[];
  srcMap: {
    raw: (x: string) => string;
    lineSplitted: (x: string) => string[];
  };
  uriPath: (x: string) => string;
};


const parseMultiLineJs = (a: string) =>
  a
    .split("\n")
    .filter((x) => x != "")
    .map((x) => JSON.parse(x));

const getVertexWithLabel = (a: Element[], label: string) =>
  a.filter((x) => x.type == "vertex" && x.label == label);

const getEdgeWithLabel = (a: Element[], label: string) =>
  a.filter((x) => x.type == "edge" && x.label == label) as Edge[];

const buildItemMap = (items: Element[]) => {
  const result: Map<Id, Element> = new Map();
  items.forEach((item) => {
    result.set(item.id, item);
  });
  return result;
};

const buildInVMap = (items: Element[]) => {
  const result: Map<Id, Element[]> = new Map();
  const insert = (x: Element, id: Id) => {
    let cur = result.get(id) ?? [];
    result.set(id, cur);
    cur.push(x);
  };
  items.forEach((item: Element) => {
    if (item.type != 'edge') {
      return;
    }
    if ('inV' in item) insert(item, item.inV);
    if ('inVs' in item) item.inVs.forEach((id: Id) => insert(item, id));
  });
  return result;
};


const buildOutVMap = (items: Element[]) => {
  const result: Map<Id, Element[]> = new Map();
  items.forEach((item) => {
    if (item.type != 'edge' || !item.outV) {
      return;
    }
    let cur = result.get(item.outV) ?? [];
    result.set(item.outV, cur);
    cur.push(item);
  });
  return result;
};

function getFromMap<A, B>(map: Map<A, B>): SafeMap<A, B> {
  return {
    get: (id: A) => {
      const x = map.get(id);
      if (!x) throw new Error(`map doesn't have ${id}`);
      return x;
    },
    have: (id: A) => map.get(id) !== undefined,
  };
}

const buildSrcMap = async (documents: Document[]) => {
  const raw: Map<string, string> = new Map();
  const lineSplitted: Map<string, string[]> = new Map();
  await Promise.all(documents.map(async (doc) => {
    const srcRaw = (await readFile(doc.uri.slice(7))).toString();
    raw.set(doc.uri, srcRaw);
    lineSplitted.set(doc.uri, srcRaw.split('\n'));
  }));
  return { raw: getFromMap(raw).get, lineSplitted: getFromMap(lineSplitted).get };
};

const buildUriMap = async (uriMapAddress: string | undefined, documents: Document[], projectRoot: string) => {
  const r: Map<string, string> = new Map();
  if (uriMapAddress) {
    const f = JSON.parse((await readFile(uriMapAddress)).toString());
    documents.map((doc) => doc.uri).forEach((x) => {
      const y = f[x];
      if (!y) {
        r.set(x, `notmapped${x.slice(7)}`);
        return;
      }
      r.set(x, y);
    });
    return getFromMap(r);
  }
  documents.map((doc) => doc.uri).forEach((x) => {
    if (!x.startsWith(projectRoot)) {
      r.set(x, `notmapped${x.slice(7)}`);
      return;
    }
    const y = x.slice(projectRoot.length + 1);
    r.set(x, y);
  });
  return getFromMap(r);
};

export const lsifParser = async (address: string, uriMapAddress: string | undefined): Promise<Lsif> => {
  const text = (await readFile(address)).toString();
  const items: Element[] = parseMultiLineJs(text);
  const item = getFromMap(buildItemMap(items));
  const inV = getFromMap(buildInVMap(items));
  const outV = getFromMap(buildOutVMap(items));
  const { projectRoot } = getVertexWithLabel(items, "metaData")[0] as MetaData;
  const documents = getVertexWithLabel(items, "document") as Document[];
  const srcMap = await buildSrcMap(documents);
  const uriMap = await buildUriMap(uriMapAddress, documents, projectRoot);
  return {
    items, item, inV, outV, documents, srcMap, uriPath: uriMap.get,
  };
};

export const findRecursiveEdge = (lsif: Lsif, v: Id, label: string) => {
  const { outV, item } = lsif;
  for (; ;) {
    if (!outV.have(v)) return;
    const out = outV.get(v);
    const direct = getEdgeWithLabel(out, label).filter(Edge.is11);
    if (direct.length > 0) return item.get(direct[0].inV);
    const next = getEdgeWithLabel(out, 'next') as next[];
    if (next.length == 0) return;
    v = next[0].inV;
  }
};