<commit_before>import debugModule from "debug";
const debug = debugModule("debugger:stacktrace:sagas");

import { put, takeEvery, select } from "redux-saga/effects";
import { prefixName } from "lib/helpers";

import * as actions from "../actions";
import { TICK } from "lib/trace/actions";
import * as trace from "lib/trace/sagas";

import stacktrace from "../selectors";

function* tickSaga() {
  yield* stacktraceSaga();
  yield* trace.signalTickSagaCompletion();
}

function* stacktraceSaga() {
  //different possible outcomes:
  const {
    source: { id, compilationId, sourcePath },
    sourceRange
  } = yield select(stacktrace.current.location);
  const source = { id, compilationId, sourcePath }; //leave out everything else
  const currentLocation = { source, sourceRange }; //leave out the node
  const lastLocation = yield select(stacktrace.current.lastPosition); //get this upfront due to sequencing issues
  let returnedRightNow = false;
  let positionUpdated = false;
  if (yield select(stacktrace.current.willJumpIn)) {
    const nextLocation = yield select(stacktrace.next.location);
    yield put(actions.jumpIn(currentLocation, nextLocation.node)); //in this case we only want the node
    positionUpdated = true;
  } else if (yield select(stacktrace.current.willJumpOut)) {
    yield put(actions.jumpOut(currentLocation));
    positionUpdated = true;
  } else if (yield select(stacktrace.current.willCall)) {
    //an external frame marked "skip in reports" will be, for reporting
    //purposes, combined with the frame above, unless that also is a
    //marker frame (combined in the appropriate sense)
    //note: includes creations
    const skipInReports = yield select(
      stacktrace.current.nextFrameIsSkippedInReports
    );
    yield put(actions.externalCall(currentLocation, skipInReports));
    positionUpdated = true;
  } else if (yield select(stacktrace.current.willReturnOrFail)) {
    const status = yield select(stacktrace.current.returnStatus);
    debug("returning!");
    yield put(actions.externalReturn(lastLocation, status, currentLocation));
    returnedRightNow = true;
    positionUpdated = true;
  }
  //we'll handle this next case separately of the above,
  //so notionally 2 actions could fire, but it's pretty unlikely
  if (
    !returnedRightNow && //otherwise this will trigger in an inconsistent state
    (yield select(stacktrace.current.returnCounter)) > 0 &&
    (yield select(stacktrace.current.positionWillChange))
  ) {
    debug("executing!");
    debug("location: %o", yield select(stacktrace.next.location));
    debug("marked: %o", yield select(stacktrace.current.lastPosition));
    const returnCounter = yield select(stacktrace.current.returnCounter);
    yield put(actions.executeReturn(returnCounter, currentLocation));
    positionUpdated = true;
  }
  if (!positionUpdated) {
    //finally, if no other action updated the position, do so here
    yield put(actions.updatePosition(currentLocation));
  }
}

export function* reset() {
  yield put(actions.reset());
}

export function* unload() {
  yield put(actions.unloadTransaction());
}

export function* begin() {
  const skipInReports = yield select(
    stacktrace.current.nextFrameIsSkippedInReports
  );
  yield put(actions.externalCall(null, skipInReports));
}

export function* saga() {
  yield takeEvery(TICK, tickSaga);
}

export default prefixName("stacktrace", saga);
<commit_msg>Resequence stacktrace saga to handle edge cases<commit_after>import debugModule from "debug";
const debug = debugModule("debugger:stacktrace:sagas");

import { put, takeEvery, select } from "redux-saga/effects";
import { prefixName } from "lib/helpers";

import * as actions from "../actions";
import { TICK } from "lib/trace/actions";
import * as trace from "lib/trace/sagas";

import stacktrace from "../selectors";

function* tickSaga() {
  yield* stacktraceSaga();
  yield* trace.signalTickSagaCompletion();
}

function* stacktraceSaga() {
  //different possible outcomes:
  const {
    source: { id, compilationId, sourcePath },
    sourceRange
  } = yield select(stacktrace.current.location);
  const source = { id, compilationId, sourcePath }; //leave out everything else
  const currentLocation = { source, sourceRange }; //leave out the node
  const lastLocation = yield select(stacktrace.current.lastPosition);
  let positionUpdated = false;
  //first: are we returning?
  if (yield select(stacktrace.current.willReturnOrFail)) {
    const status = yield select(stacktrace.current.returnStatus);
    debug("returning!");
    yield put(actions.externalReturn(lastLocation, status, currentLocation));
    positionUpdated = true;
  } else if (
    //next: are we *executing* a return?
    //note this needs to be an else if or else this could execute
    //in an inconsistent state
    (yield select(stacktrace.current.returnCounter)) > 0 &&
    (yield select(stacktrace.current.positionWillChange))
  ) {
    debug("executing!");
    debug("location: %o", yield select(stacktrace.next.location));
    debug("marked: %o", lastLocation);
    const returnCounter = yield select(stacktrace.current.returnCounter);
    yield put(actions.executeReturn(returnCounter, currentLocation));
    positionUpdated = true;
  }
  //we now process the other possibilities.
  //technically, an EXECUTE_RETURN could happen as well as those below,
  //resulting in 2 actions instead of just one, but it's pretty unlikely.
  //(an EXTERNAL_RETURN, OTOH, is obviously exclusive of the possibilities below)
  if (yield select(stacktrace.current.willJumpIn)) {
    const nextLocation = yield select(stacktrace.next.location);
    yield put(actions.jumpIn(currentLocation, nextLocation.node)); //in this case we only want the node
    positionUpdated = true;
  } else if (yield select(stacktrace.current.willJumpOut)) {
    yield put(actions.jumpOut(currentLocation));
    positionUpdated = true;
  } else if (yield select(stacktrace.current.willCall)) {
    //an external frame marked "skip in reports" will be, for reporting
    //purposes, combined with the frame above, unless that also is a
    //marker frame (combined in the appropriate sense)
    //note: includes creations
    const skipInReports = yield select(
      stacktrace.current.nextFrameIsSkippedInReports
    );
    yield put(actions.externalCall(currentLocation, skipInReports));
    positionUpdated = true;
  }
  //finally, if no other action updated the position, do so here
  if (!positionUpdated) {
    yield put(actions.updatePosition(currentLocation));
  }
}

export function* reset() {
  yield put(actions.reset());
}

export function* unload() {
  yield put(actions.unloadTransaction());
}

export function* begin() {
  const skipInReports = yield select(
    stacktrace.current.nextFrameIsSkippedInReports
  );
  yield put(actions.externalCall(null, skipInReports));
}

export function* saga() {
  yield takeEvery(TICK, tickSaga);
}

export default prefixName("stacktrace", saga);

{'fraction_non_alphanumeric': 0.10996119016817593, 'fraction_numerical': 0.0005749604714675866, 'mean_word_length': 4.410575427682737, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6532958', 'n_tokens_mistral': 1971, 'n_tokens_neox': 1937, 'n_words': 584}