Backed out 8 changesets (bug 1541631) for permafailing in debugger/cold-open.js CLOSED TREE

Backed out changeset bc99fb7a9d12 (bug 1541631)
Backed out changeset 696e24030686 (bug 1541631)
Backed out changeset 6db731f26958 (bug 1541631)
Backed out changeset 9f928da98d32 (bug 1541631)
Backed out changeset 53b83b8e37e0 (bug 1541631)
Backed out changeset 1d7a76a1fac8 (bug 1541631)
Backed out changeset 4f94c700f977 (bug 1541631)
Backed out changeset 41f6c078e950 (bug 1541631)

--HG--
rename : devtools/client/debugger/src/actions/tests/helpers/mockCommandClient.js => devtools/client/debugger/src/actions/tests/helpers/threadFront.js
This commit is contained in:
Noemi Erli 2019-08-16 00:41:23 +03:00
Родитель 5e47b0f499
Коммит 5759dc0940
65 изменённых файлов: 703 добавлений и 856 удалений

Просмотреть файл

@ -32,7 +32,10 @@ function getOutOfScopeLines(outOfScopeLocations: ?(AstLocation[])) {
}
async function getInScopeLines(cx, location, { dispatch, getState, parser }) {
const source = getSourceWithContent(getState(), location.sourceId);
const { source, content } = getSourceWithContent(
getState(),
location.sourceId
);
let locations = null;
if (location.line && source && !source.isWasm) {
@ -44,9 +47,7 @@ async function getInScopeLines(cx, location, { dispatch, getState, parser }) {
const linesOutOfScope = getOutOfScopeLines(locations);
const sourceNumLines =
!source.content || !isFulfilled(source.content)
? 0
: getSourceLineCount(source.content.value);
!content || !isFulfilled(content) ? 0 : getSourceLineCount(content.value);
const sourceLines = range(1, sourceNumLines + 1);

Просмотреть файл

@ -21,20 +21,20 @@ const sourceTexts = {
"scopes.js": readFixture("scopes.js"),
};
const mockCommandClient = {
const threadFront = {
sourceContents: async ({ source }) => ({
source: sourceTexts[source],
contentType: "text/javascript",
}),
evaluateExpressions: async () => {},
getFrameScopes: async () => {},
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
};
describe("getInScopeLine", () => {
it("with selected line", async () => {
const store = createStore(mockCommandClient);
const store = createStore(threadFront);
const { dispatch, getState } = store;
const source = makeMockSource("scopes.js", "scopes.js");

Просмотреть файл

@ -14,6 +14,8 @@ import { uniqBy, zip } from "lodash";
import {
getSource,
getSourceFromId,
hasBreakpointPositions,
hasBreakpointPositionsForLine,
getBreakpointPositionsForSource,
getSourceActorsForSource,
} from "../../selectors";
@ -31,9 +33,7 @@ import {
memoizeableAction,
type MemoizedAction,
} from "../../utils/memoizableAction";
import { fulfilled } from "../../utils/async-value";
import type { ThunkArgs } from "../../actions/types";
import { loadSourceActorBreakpointColumns } from "../source-actors";
async function mapLocations(
generatedLocations: SourceLocation[],
@ -114,7 +114,7 @@ async function _setBreakpointPositions(cx, sourceId, line, thunkArgs) {
return;
}
const results = {};
let results = {};
if (isOriginalId(sourceId)) {
// Explicitly typing ranges is required to work around the following issue
// https://github.com/facebook/flow/issues/5294
@ -140,22 +140,12 @@ async function _setBreakpointPositions(cx, sourceId, line, thunkArgs) {
};
}
const actorBps = await Promise.all(
getSourceActorsForSource(getState(), generatedSource.id).map(actor =>
client.getSourceActorBreakpointPositions(actor, range)
)
const bps = await client.getBreakpointPositions(
getSourceActorsForSource(getState(), generatedSource.id),
range
);
for (const actorPositions of actorBps) {
for (const rangeLine of Object.keys(actorPositions)) {
let columns = actorPositions[parseInt(rangeLine, 10)];
const existing = results[rangeLine];
if (existing) {
columns = [...new Set([...existing, ...columns])];
}
results[rangeLine] = columns;
}
for (const bpLine in bps) {
results[bpLine] = (results[bpLine] || []).concat(bps[bpLine]);
}
}
} else {
@ -163,15 +153,10 @@ async function _setBreakpointPositions(cx, sourceId, line, thunkArgs) {
throw new Error("Line is required for generated sources");
}
const actorColumns = await Promise.all(
getSourceActorsForSource(getState(), generatedSource.id).map(actor =>
dispatch(loadSourceActorBreakpointColumns({ id: actor.id, line }))
)
results = await client.getBreakpointPositions(
getSourceActorsForSource(getState(), generatedSource.id),
{ start: { line, column: 0 }, end: { line: line + 1, column: 0 } }
);
for (const columns of actorColumns) {
results[line] = (results[line] || []).concat(columns);
}
}
let positions = convertToList(results, generatedSource);
@ -193,6 +178,8 @@ async function _setBreakpointPositions(cx, sourceId, line, thunkArgs) {
source: source,
positions,
});
return positions;
}
function generatedSourceActorKey(state, sourceId) {
@ -212,20 +199,12 @@ export const setBreakpointPositions: MemoizedAction<
{ cx: Context, sourceId: string, line?: number },
?BreakpointPositions
> = memoizeableAction("setBreakpointPositions", {
getValue: ({ sourceId, line }, { getState }) => {
const positions = getBreakpointPositionsForSource(getState(), sourceId);
if (!positions) {
return null;
}
if (isGeneratedId(sourceId) && line && !positions[line]) {
// We always return the full position dataset, but if a given line is
// not available, we treat the whole set as loading.
return null;
}
return fulfilled(positions);
},
hasValue: ({ sourceId, line }, { getState }) =>
isGeneratedId(sourceId) && line
? hasBreakpointPositionsForLine(getState(), sourceId, line)
: hasBreakpointPositions(getState(), sourceId),
getValue: ({ sourceId, line }, { getState }) =>
getBreakpointPositionsForSource(getState(), sourceId),
createKey({ sourceId, line }, { getState }) {
const key = generatedSourceActorKey(getState(), sourceId);
return isGeneratedId(sourceId) && line ? `${key}-${line}` : key;

Просмотреть файл

@ -11,15 +11,15 @@ import {
makeSource,
waitForState,
} from "../../../utils/test-head";
import { createSource } from "../../tests/helpers/mockCommandClient";
import { createSource } from "../../tests/helpers/threadFront";
describe("breakpointPositions", () => {
it("fetches positions", async () => {
const fooContent = createSource("foo", "");
const store = createStore({
getSourceActorBreakpointPositions: async () => ({ "9": [1] }),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({ "9": [1] }),
getBreakableLines: async () => [],
sourceContents: async () => fooContent,
});
@ -63,12 +63,12 @@ describe("breakpointPositions", () => {
let resolve = _ => {};
let count = 0;
const store = createStore({
getSourceActorBreakpointPositions: () =>
getBreakpointPositions: () =>
new Promise(r => {
count++;
resolve = r;
}),
getSourceActorBreakableLines: async () => [],
getBreakableLines: async () => [],
sourceContents: async () => fooContent,
});

Просмотреть файл

@ -12,13 +12,13 @@ import {
getTelemetryEvents,
} from "../../../utils/test-head";
import { mockCommandClient } from "../../tests/helpers/mockCommandClient";
import { simpleMockThreadFront } from "../../tests/helpers/threadFront.js";
function mockClient(positionsResponse = {}) {
return {
...mockCommandClient,
getSourceActorBreakpointPositions: async () => positionsResponse,
getSourceActorBreakableLines: async () => [],
...simpleMockThreadFront,
getBreakpointPositions: async () => positionsResponse,
getBreakableLines: async () => [],
};
}

Просмотреть файл

@ -35,8 +35,8 @@ type Match = Object;
export function doSearch(cx: Context, query: string, editor: Editor) {
return ({ getState, dispatch }: ThunkArgs) => {
const selectedSource = getSelectedSourceWithContent(getState());
if (!selectedSource || !selectedSource.content) {
const selectedSourceWithContent = getSelectedSourceWithContent(getState());
if (!selectedSourceWithContent || !selectedSourceWithContent.content) {
return;
}
@ -52,8 +52,8 @@ export function doSearchForHighlight(
ch: number
) {
return async ({ getState, dispatch }: ThunkArgs) => {
const selectedSource = getSelectedSourceWithContent(getState());
if (!selectedSource || !selectedSource.content) {
const selectedSourceWithContent = getSelectedSourceWithContent(getState());
if (!selectedSourceWithContent || !selectedSourceWithContent.content) {
return;
}
dispatch(searchContentsForHighlight(query, editor, line, ch));
@ -105,18 +105,19 @@ export function searchContents(
) {
return async ({ getState, dispatch }: ThunkArgs) => {
const modifiers = getFileSearchModifiers(getState());
const selectedSource = getSelectedSourceWithContent(getState());
const selectedSourceWithContent = getSelectedSourceWithContent(getState());
if (
!editor ||
!selectedSource ||
!selectedSource.content ||
!isFulfilled(selectedSource.content) ||
!selectedSourceWithContent ||
!selectedSourceWithContent.content ||
!isFulfilled(selectedSourceWithContent.content) ||
!modifiers
) {
return;
}
const selectedContent = selectedSource.content.value;
const selectedSource = selectedSourceWithContent.source;
const selectedContent = selectedSourceWithContent.content.value;
const ctx = { ed: editor, cm: editor.codeMirror };

Просмотреть файл

@ -22,7 +22,7 @@ import { features } from "../../../utils/prefs";
const { isStepping } = selectors;
let stepInResolve = null;
const mockCommandClient = {
const mockThreadFront = {
stepIn: () =>
new Promise(_resolve => {
stepInResolve = _resolve;
@ -71,8 +71,8 @@ const mockCommandClient = {
}
});
},
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
actorID: "threadActorID",
};
@ -104,7 +104,7 @@ function createPauseInfo(
describe("pause", () => {
describe("stepping", () => {
it("should set and clear the command", async () => {
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(mockThreadFront);
const mockPauseInfo = createPauseInfo();
await dispatch(actions.newGeneratedSource(makeSource("foo1")));
@ -129,7 +129,7 @@ describe("pause", () => {
});
it("should step when paused", async () => {
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(mockThreadFront);
const mockPauseInfo = createPauseInfo();
await dispatch(actions.newGeneratedSource(makeSource("foo1")));
@ -140,7 +140,7 @@ describe("pause", () => {
});
it("should step over when paused", async () => {
const store = createStore(mockCommandClient);
const store = createStore(mockThreadFront);
const { dispatch, getState } = store;
const mockPauseInfo = createPauseInfo();
@ -155,7 +155,7 @@ describe("pause", () => {
it("should step over when paused before an await", async () => {
features.asyncStepping = true;
const store = createStore(mockCommandClient);
const store = createStore(mockThreadFront);
const { dispatch, getState } = store;
const mockPauseInfo = createPauseInfo({
sourceId: "await",
@ -175,8 +175,8 @@ describe("pause", () => {
it("should step over when paused after an await", async () => {
const store = createStore({
...mockCommandClient,
getSourceActorBreakpointPositions: async () => ({ [2]: [1] }),
...mockThreadFront,
getBreakpointPositions: async () => ({ [2]: [1] }),
});
const { dispatch, getState } = store;
const mockPauseInfo = createPauseInfo({
@ -202,7 +202,7 @@ describe("pause", () => {
column: 0,
};
const store = createStore(mockCommandClient, {});
const store = createStore(mockThreadFront, {});
const { dispatch, getState } = store;
const mockPauseInfo = createPauseInfo(generatedLocation, {
scope: {
@ -279,7 +279,7 @@ describe("pause", () => {
getGeneratedLocation: async location => location,
};
const store = createStore(mockCommandClient, {}, sourceMapsMock);
const store = createStore(mockThreadFront, {}, sourceMapsMock);
const { dispatch, getState } = store;
const mockPauseInfo = createPauseInfo(generatedLocation);
@ -340,7 +340,7 @@ describe("pause", () => {
getGeneratedRangesForOriginal: async () => [],
};
const store = createStore(mockCommandClient, {}, sourceMapsMock);
const store = createStore(mockThreadFront, {}, sourceMapsMock);
const { dispatch, getState } = store;
const mockPauseInfo = createPauseInfo(generatedLocation);
@ -383,7 +383,7 @@ describe("pause", () => {
describe("resumed", () => {
it("should not evaluate expression while stepping", async () => {
const client = { ...mockCommandClient, evaluateExpressions: jest.fn() };
const client = { ...mockThreadFront, evaluateExpressions: jest.fn() };
const { dispatch, getState } = createStore(client);
const mockPauseInfo = createPauseInfo();
@ -392,12 +392,12 @@ describe("pause", () => {
const cx = selectors.getThreadContext(getState());
dispatch(actions.stepIn(cx));
await dispatch(actions.resumed(mockCommandClient.actorID));
await dispatch(actions.resumed(mockThreadFront.actorID));
expect(client.evaluateExpressions.mock.calls).toHaveLength(1);
});
it("resuming - will re-evaluate watch expressions", async () => {
const client = { ...mockCommandClient, evaluateExpressions: jest.fn() };
const client = { ...mockThreadFront, evaluateExpressions: jest.fn() };
const store = createStore(client);
const { dispatch, getState, cx } = store;
const mockPauseInfo = createPauseInfo();
@ -410,7 +410,7 @@ describe("pause", () => {
client.evaluateExpressions.mockReturnValue(Promise.resolve(["YAY"]));
await dispatch(actions.paused(mockPauseInfo));
await dispatch(actions.resumed(mockCommandClient.actorID));
await dispatch(actions.resumed(mockThreadFront.actorID));
const expression = selectors.getExpression(getState(), "foo");
expect(expression && expression.value).toEqual("YAY");
});

Просмотреть файл

@ -5,18 +5,7 @@
// @flow
import type { ThunkArgs } from "./types";
import {
getSourceActor,
getSourceActorBreakableLines,
getSourceActorBreakpointColumns,
type SourceActorId,
type SourceActor,
} from "../reducers/source-actors";
import {
memoizeableAction,
type MemoizedAction,
} from "../utils/memoizableAction";
import { PROMISE } from "./utils/middleware/promise";
import type { SourceActor } from "../reducers/source-actors";
export function insertSourceActor(item: SourceActor) {
return insertSourceActors([item]);
@ -41,48 +30,3 @@ export function removeSourceActors(items: Array<SourceActor>) {
});
};
}
export const loadSourceActorBreakpointColumns: MemoizedAction<
{ id: SourceActorId, line: number },
Array<number>
> = memoizeableAction("loadSourceActorBreakpointColumns", {
createKey: ({ id, line }) => `${id}:${line}`,
getValue: ({ id, line }, { getState }) =>
getSourceActorBreakpointColumns(getState(), id, line),
action: async ({ id, line }, { dispatch, getState, client }) => {
await dispatch({
type: "SET_SOURCE_ACTOR_BREAKPOINT_COLUMNS",
sourceId: id,
line,
[PROMISE]: (async () => {
const positions = await client.getSourceActorBreakpointPositions(
getSourceActor(getState(), id),
{
start: { line, column: 0 },
end: { line: line + 1, column: 0 },
}
);
return positions[line] || [];
})(),
});
},
});
export const loadSourceActorBreakableLines: MemoizedAction<
{ id: SourceActorId },
Array<number>
> = memoizeableAction("loadSourceActorBreakableLines", {
createKey: args => args.id,
getValue: ({ id }, { getState }) =>
getSourceActorBreakableLines(getState(), id),
action: async ({ id }, { dispatch, getState, client }) => {
await dispatch({
type: "SET_SOURCE_ACTOR_BREAKABLE_LINES",
sourceId: id,
[PROMISE]: client.getSourceActorBreakableLines(
getSourceActor(getState(), id)
),
});
},
});

Просмотреть файл

@ -10,7 +10,6 @@ import { setBreakpointPositions } from "../breakpoints/breakpointPositions";
import { union } from "lodash";
import type { Context } from "../../types";
import type { ThunkArgs } from "../../actions/types";
import { loadSourceActorBreakableLines } from "../source-actors";
function calculateBreakableLines(positions) {
const lines = [];
@ -31,26 +30,22 @@ export function setBreakableLines(cx: Context, sourceId: string) {
setBreakpointPositions({ cx, sourceId })
);
breakableLines = calculateBreakableLines(positions);
const existingBreakableLines = getBreakableLines(getState(), sourceId);
if (existingBreakableLines) {
breakableLines = union(existingBreakableLines, breakableLines);
}
dispatch({
type: "SET_ORIGINAL_BREAKABLE_LINES",
cx,
sourceId,
breakableLines,
});
} else {
const actors = getSourceActorsForSource(getState(), sourceId);
await Promise.all(
actors.map(actor =>
dispatch(loadSourceActorBreakableLines({ id: actor.id }))
)
breakableLines = await client.getBreakableLines(
getSourceActorsForSource(getState(), sourceId)
);
}
const existingBreakableLines = getBreakableLines(getState(), sourceId);
if (existingBreakableLines) {
breakableLines = union(existingBreakableLines, breakableLines);
}
dispatch({
type: "SET_BREAKABLE_LINES",
cx,
sourceId,
breakableLines,
});
};
}

Просмотреть файл

@ -19,7 +19,7 @@ import { addBreakpoint } from "../breakpoints";
import { prettyPrintSource } from "./prettyPrint";
import { setBreakableLines } from "./breakableLines";
import { isFulfilled, fulfilled } from "../../utils/async-value";
import { isFulfilled } from "../../utils/async-value";
import { isOriginal, isPretty } from "../../utils/source";
import {
@ -124,6 +124,8 @@ async function loadSourceTextPromise(
await dispatch(addBreakpoint(cx, location, options, disabled));
}
}
return newSource;
}
export function loadSourceById(cx: Context, sourceId: string) {
@ -137,22 +139,14 @@ export const loadSourceText: MemoizedAction<
{ cx: Context, source: Source },
?Source
> = memoizeableAction("loadSourceText", {
getValue: ({ source }, { getState }) => {
source = source ? getSource(getState(), source.id) : null;
if (!source) {
return null;
}
const { content } = getSourceWithContent(getState(), source.id);
if (!content || content.state === "pending") {
return content;
}
// This currently swallows source-load-failure since we return fulfilled
// here when content.state === "rejected". In an ideal world we should
// propagate that error upward.
return fulfilled(source);
exitEarly: ({ source }) => !source,
hasValue: ({ source }, { getState }) => {
return !!(
getSource(getState(), source.id) &&
getSourceWithContent(getState(), source.id).content
);
},
getValue: ({ source }, { getState }) => getSource(getState(), source.id),
createKey: ({ source }, { getState }) => {
const epoch = getSourcesEpoch(getState());
return `${epoch}:${source.id}`;

Просмотреть файл

@ -66,7 +66,7 @@ export function createPrettySource(cx: Context, sourceId: string) {
const url = getPrettySourceURL(source.url);
const id = generatedToOriginalId(sourceId, url);
const prettySource = {
const prettySource: Source = {
id,
url,
relativeUrl: url,

Просмотреть файл

@ -4,7 +4,7 @@
// @flow
import { getSymbols } from "../../selectors";
import { hasSymbols, getSymbols } from "../../selectors";
import { PROMISE } from "../utils/middleware/promise";
import { updateTab } from "../tabs";
@ -14,7 +14,6 @@ import {
memoizeableAction,
type MemoizedAction,
} from "../../utils/memoizableAction";
import { fulfilled } from "../../utils/async-value";
import type { Source, Context } from "../../types";
import type { Symbols } from "../../reducers/types";
@ -35,6 +34,8 @@ async function doSetSymbols(cx, source, { dispatch, getState, parser }) {
if (symbols && symbols.framework) {
dispatch(updateTab(source, symbols.framework));
}
return symbols;
}
type Args = { cx: Context, source: Source };
@ -42,18 +43,9 @@ type Args = { cx: Context, source: Source };
export const setSymbols: MemoizedAction<Args, ?Symbols> = memoizeableAction(
"setSymbols",
{
getValue: ({ source }, { getState }) => {
if (source.isWasm) {
return fulfilled(null);
}
const symbols = getSymbols(getState(), source);
if (!symbols || symbols.loading) {
return null;
}
return fulfilled(symbols);
},
exitEarly: ({ source }) => source.isWasm,
hasValue: ({ source }, { getState }) => hasSymbols(getState(), source),
getValue: ({ source }, { getState }) => getSymbols(getState(), source),
createKey: ({ source }) => source.id,
action: ({ cx, source }, thunkArgs) => doSetSymbols(cx, source, thunkArgs),
}

Просмотреть файл

@ -15,7 +15,7 @@ describe("blackbox", () => {
it("should blackbox a source", async () => {
const store = createStore({
blackBox: async () => true,
getSourceActorBreakableLines: async () => [],
getBreakableLines: async () => [],
});
const { dispatch, getState, cx } = store;

Просмотреть файл

@ -14,14 +14,14 @@ import {
} from "../../../utils/test-head";
import {
createSource,
mockCommandClient,
} from "../../tests/helpers/mockCommandClient";
sourceThreadFront,
} from "../../tests/helpers/threadFront.js";
import { getBreakpointsList } from "../../../selectors";
import { isFulfilled, isRejected } from "../../../utils/async-value";
describe("loadSourceText", () => {
it("should load source text", async () => {
const store = createStore(mockCommandClient);
const store = createStore(sourceThreadFront);
const { dispatch, getState, cx } = store;
const foo1Source = await dispatch(
@ -59,10 +59,10 @@ describe("loadSourceText", () => {
const store = createStore(
{
...mockCommandClient,
...sourceThreadFront,
sourceContents: async () => fooGenContent,
getSourceActorBreakpointPositions: async () => ({ "1": [0] }),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({ "1": [0] }),
getBreakableLines: async () => [],
},
{},
{
@ -156,8 +156,8 @@ describe("loadSourceText", () => {
count++;
resolve = r;
}),
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
});
const id = "foo";
@ -194,8 +194,8 @@ describe("loadSourceText", () => {
count++;
resolve = r;
}),
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
});
const id = "foo";
@ -223,7 +223,7 @@ describe("loadSourceText", () => {
});
it("should cache subsequent source text loads", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const source = await dispatch(
actions.newGeneratedSource(makeSource("foo1"))
@ -238,7 +238,7 @@ describe("loadSourceText", () => {
});
it("should indicate a loading source", async () => {
const store = createStore(mockCommandClient);
const store = createStore(sourceThreadFront);
const { dispatch, cx } = store;
const source = await dispatch(
@ -255,7 +255,7 @@ describe("loadSourceText", () => {
});
it("should indicate an errored source text", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const source = await dispatch(
actions.newGeneratedSource(makeSource("bad-id"))

Просмотреть файл

@ -20,11 +20,12 @@ const {
} = selectors;
import sourceQueue from "../../../utils/source-queue";
import { mockCommandClient } from "../../tests/helpers/mockCommandClient";
// eslint-disable-next-line max-len
import { sourceThreadFront as threadFront } from "../../tests/helpers/threadFront.js";
describe("sources - new sources", () => {
it("should add sources to state", async () => {
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("base.js")));
await dispatch(actions.newGeneratedSource(makeSource("jquery.js")));
@ -36,7 +37,7 @@ describe("sources - new sources", () => {
});
it("should not add multiple identical sources", async () => {
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("base.js")));
await dispatch(actions.newGeneratedSource(makeSource("base.js")));
@ -45,7 +46,7 @@ describe("sources - new sources", () => {
});
it("should automatically select a pending source", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
const baseSourceURL = makeSourceURL("base.js");
await dispatch(actions.selectSourceURL(cx, baseSourceURL));
@ -60,7 +61,7 @@ describe("sources - new sources", () => {
it("should add original sources", async () => {
const { dispatch, getState } = createStore(
mockCommandClient,
threadFront,
{},
{
getOriginalURLs: async () => ["magic.js"],
@ -81,7 +82,7 @@ describe("sources - new sources", () => {
it("should not attempt to fetch original sources if it's missing a source map url", async () => {
const getOriginalURLs = jest.fn();
const { dispatch } = createStore(
mockCommandClient,
threadFront,
{},
{
getOriginalURLs,
@ -96,7 +97,7 @@ describe("sources - new sources", () => {
// eslint-disable-next-line
it("should process new sources immediately, without waiting for source maps to be fetched first", async () => {
const { dispatch, getState } = createStore(
mockCommandClient,
threadFront,
{},
{
getOriginalURLs: async () => new Promise(_ => {}),
@ -116,7 +117,7 @@ describe("sources - new sources", () => {
// eslint-disable-next-line
it("shouldn't let one slow loading source map delay all the other source maps", async () => {
const dbg = createStore(
mockCommandClient,
threadFront,
{},
{
getOriginalURLs: async source => {
@ -152,7 +153,7 @@ describe("sources - new sources", () => {
it(`should find two sources when same source with
querystring`, async () => {
const { getSourcesUrlsInSources } = selectors;
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("base.js?v=1")));
await dispatch(actions.newGeneratedSource(makeSource("base.js?v=2")));
await dispatch(actions.newGeneratedSource(makeSource("diff.js?v=1")));

Просмотреть файл

@ -11,12 +11,12 @@ import {
makeSource,
} from "../../../utils/test-head";
import { createPrettySource } from "../prettyPrint";
import { mockCommandClient } from "../../tests/helpers/mockCommandClient";
import { sourceThreadFront } from "../../tests/helpers/threadFront.js";
import { isFulfilled } from "../../../utils/async-value";
describe("sources - pretty print", () => {
it("returns a pretty source for a minified file", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const url = "base.js";
const source = await dispatch(actions.newGeneratedSource(makeSource(url)));
@ -42,7 +42,7 @@ describe("sources - pretty print", () => {
});
it("should create a source when first toggling pretty print", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const source = await dispatch(
actions.newGeneratedSource(makeSource("foobar.js"))
@ -54,7 +54,7 @@ describe("sources - pretty print", () => {
});
it("should not make a second source when toggling pretty print", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const source = await dispatch(
actions.newGeneratedSource(makeSource("foobar.js"))

Просмотреть файл

@ -13,11 +13,11 @@ import {
const { getSourcesUrlsInSources } = selectors;
// eslint-disable-next-line max-len
import { mockCommandClient } from "../../tests/helpers/mockCommandClient";
import { sourceThreadFront as threadFront } from "../../tests/helpers/threadFront.js";
describe("sources - sources with querystrings", () => {
it("should find two sources when same source with querystring", async () => {
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("base.js?v=1")));
await dispatch(actions.newGeneratedSource(makeSource("base.js?v=2")));
await dispatch(actions.newGeneratedSource(makeSource("diff.js?v=1")));

Просмотреть файл

@ -23,7 +23,7 @@ const {
getSelectedLocation,
} = selectors;
import { mockCommandClient } from "../../tests/helpers/mockCommandClient";
import { sourceThreadFront } from "../../tests/helpers/threadFront.js";
process.on("unhandledRejection", (reason, p) => {});
@ -35,7 +35,7 @@ describe("sources", () => {
it("should select a source", async () => {
// Note that we pass an empty client in because the action checks
// if it exists.
const store = createStore(mockCommandClient);
const store = createStore(sourceThreadFront);
const { dispatch, getState } = store;
const frame = makeFrame({ id: "1", sourceId: "foo1" });
@ -69,7 +69,7 @@ describe("sources", () => {
});
it("should select next tab on tab closed if no previous tab", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const fooSource = await dispatch(
actions.newGeneratedSource(makeSource("foo.js"))
@ -98,7 +98,7 @@ describe("sources", () => {
});
it("should open a tab for the source", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
dispatch(actions.selectLocation(cx, initialLocation("foo.js")));
@ -108,7 +108,7 @@ describe("sources", () => {
});
it("should select previous tab on tab closed", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
await dispatch(actions.newGeneratedSource(makeSource("bar.js")));
@ -127,7 +127,7 @@ describe("sources", () => {
});
it("should keep the selected source when other tab closed", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
await dispatch(actions.newGeneratedSource(makeSource("bar.js")));
@ -154,7 +154,7 @@ describe("sources", () => {
});
it("should not select new sources that lack a URL", async () => {
const { dispatch, getState } = createStore(mockCommandClient);
const { dispatch, getState } = createStore(sourceThreadFront);
await dispatch(
actions.newGeneratedSource({
@ -169,7 +169,7 @@ describe("sources", () => {
});
it("sets and clears selected location correctly", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const source = await dispatch(
actions.newGeneratedSource(makeSource("testSource"))
);
@ -188,7 +188,7 @@ describe("sources", () => {
});
it("sets and clears pending selected location correctly", () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const url = "testURL";
const options = { location: { line: "testLine" } };
@ -207,7 +207,7 @@ describe("sources", () => {
});
it("should keep the generated the viewing context", async () => {
const store = createStore(mockCommandClient);
const store = createStore(sourceThreadFront);
const { dispatch, getState, cx } = store;
const baseSource = await dispatch(
actions.newGeneratedSource(makeSource("base.js"))
@ -224,7 +224,7 @@ describe("sources", () => {
it("should keep the original the viewing context", async () => {
const { dispatch, getState, cx } = createStore(
mockCommandClient,
sourceThreadFront,
{},
{
getOriginalLocation: async location => ({ ...location, line: 12 }),
@ -258,7 +258,7 @@ describe("sources", () => {
it("should change the original the viewing context", async () => {
const { dispatch, getState, cx } = createStore(
mockCommandClient,
sourceThreadFront,
{},
{
getOriginalLocation: async location => ({ ...location, line: 12 }),
@ -290,7 +290,7 @@ describe("sources", () => {
describe("selectSourceURL", () => {
it("should automatically select a pending source", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(sourceThreadFront);
const baseSourceURL = makeSourceURL("base.js");
await dispatch(actions.selectSourceURL(cx, baseSourceURL));

Просмотреть файл

@ -17,7 +17,7 @@ import {
import readFixture from "./helpers/readFixture";
const { getSymbols, isSymbolsLoading, getFramework } = selectors;
const mockCommandClient = {
const threadFront = {
sourceContents: async ({ source }) => ({
source: sourceTexts[source],
contentType: "text/javascript",
@ -26,8 +26,8 @@ const mockCommandClient = {
evaluate: async expression => ({ result: evaluationResult[expression] }),
evaluateExpressions: async expressions =>
expressions.map(expression => ({ result: evaluationResult[expression] })),
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
};
const sourceMaps = {
@ -56,7 +56,7 @@ describe("ast", () => {
describe("setSymbols", () => {
describe("when the source is loaded", () => {
it("should be able to set symbols", async () => {
const store = createStore(mockCommandClient);
const store = createStore(threadFront);
const { dispatch, getState, cx } = store;
const base = await dispatch(
actions.newGeneratedSource(makeSource("base.js"))
@ -74,7 +74,7 @@ describe("ast", () => {
describe("when the source is not loaded", () => {
it("should return null", async () => {
const { getState, dispatch } = createStore(mockCommandClient);
const { getState, dispatch } = createStore(threadFront);
const base = await dispatch(
actions.newGeneratedSource(makeSource("base.js"))
);
@ -86,7 +86,7 @@ describe("ast", () => {
describe("when there is no source", () => {
it("should return null", async () => {
const { getState } = createStore(mockCommandClient);
const { getState } = createStore(threadFront);
const baseSymbols = getSymbols(getState());
expect(baseSymbols).toEqual(null);
});
@ -94,7 +94,7 @@ describe("ast", () => {
describe("frameworks", () => {
it("should detect react components", async () => {
const store = createStore(mockCommandClient, {}, sourceMaps);
const store = createStore(threadFront, {}, sourceMaps);
const { cx, dispatch, getState } = store;
const genSource = await dispatch(
@ -113,7 +113,7 @@ describe("ast", () => {
});
it("should not give false positive on non react components", async () => {
const store = createStore(mockCommandClient);
const store = createStore(threadFront);
const { cx, dispatch, getState } = store;
const base = await dispatch(
actions.newGeneratedSource(makeSource("base.js"))

Просмотреть файл

@ -37,8 +37,8 @@ const mockThreadFront = {
),
getFrameScopes: async () => {},
sourceContents: () => ({ source: "", contentType: "text/javascript" }),
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
autocomplete: () => {
return new Promise(resolve => {
resolve({

Просмотреть файл

@ -4,7 +4,11 @@
// @flow
import type { SourceActor } from "../../../types";
import type {
SourceActor,
SourceActorLocation,
BreakpointOptions,
} from "../../../types";
export function createSource(name: string, code?: string) {
name = name.replace(/\..*$/, "");
@ -32,7 +36,33 @@ const sources = [
"jquery.js",
];
export const mockCommandClient = {
export const simpleMockThreadFront = {
getBreakpointByLocation: (jest.fn(): any),
setBreakpoint: (location: SourceActorLocation, _condition: string) =>
Promise.resolve({ id: "hi", actualLocation: location }),
removeBreakpoint: (_id: string) => Promise.resolve(),
setBreakpointOptions: (
_id: string,
_location: SourceActorLocation,
_options: BreakpointOptions,
_noSliding: boolean
) => Promise.resolve({ sourceId: "a", line: 5 }),
sourceContents: ({
source,
}: SourceActor): Promise<{| source: any, contentType: ?string |}> =>
new Promise((resolve, reject) => {
if (sources.includes(source)) {
resolve(createSource(source));
}
reject(`unknown source: ${source}`);
}),
};
// sources and tabs
export const sourceThreadFront = {
sourceContents: function({
source,
}: SourceActor): Promise<{| source: any, contentType: ?string |}> {
@ -45,10 +75,9 @@ export const mockCommandClient = {
});
},
setBreakpoint: async () => {},
removeBreakpoint: (_id: string) => Promise.resolve(),
threadFront: async () => {},
getFrameScopes: async () => {},
evaluateExpressions: async () => {},
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
};

Просмотреть файл

@ -27,8 +27,8 @@ const threadFront = {
source: "function foo1() {\n const foo = 5; return foo;\n}",
contentType: "text/javascript",
}),
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
detachWorkers: () => {},
};

Просмотреть файл

@ -10,7 +10,7 @@ import {
mockPendingBreakpoint,
} from "./helpers/breakpoints.js";
import { mockCommandClient } from "./helpers/mockCommandClient";
import { simpleMockThreadFront } from "./helpers/threadFront.js";
import { asyncStore } from "../../utils/prefs";
@ -47,10 +47,10 @@ import sourceMaps from "devtools-source-map";
import { makePendingLocationId } from "../../utils/breakpoint";
function mockClient(bpPos = {}) {
return {
...mockCommandClient,
...simpleMockThreadFront,
getSourceActorBreakpointPositions: async () => bpPos,
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => bpPos,
getBreakableLines: async () => [],
};
}

Просмотреть файл

@ -31,8 +31,8 @@ function mockThreadFront(overrides) {
source: "",
contentType: "text/javascript",
}),
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
evaluateExpressions: async () => [],
loadObjectProperties: async () => ({}),
...overrides,

Просмотреть файл

@ -39,8 +39,8 @@ const sources = {
const threadFront = {
sourceContents: async ({ source }) => sources[source],
getSourceActorBreakpointPositions: async () => ({}),
getSourceActorBreakableLines: async () => [],
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
};
describe("project text search", () => {

Просмотреть файл

@ -43,7 +43,7 @@ describe("setProjectDirectoryRoot", () => {
it("should filter sources", async () => {
const store = createStore({
getSourceActorBreakableLines: async () => [],
getBreakableLines: async () => [],
});
const { dispatch, getState, cx } = store;
await dispatch(actions.newGeneratedSource(makeSource("js/scopes.js")));
@ -65,7 +65,7 @@ describe("setProjectDirectoryRoot", () => {
it("should update the child directory ", () => {
const { dispatch, getState, cx } = createStore({
getSourceActorBreakableLines: async () => [],
getBreakableLines: async () => [],
});
dispatch(actions.setProjectDirectoryRoot(cx, "example.com"));
dispatch(actions.setProjectDirectoryRoot(cx, "example.com/foo/bar"));
@ -74,7 +74,7 @@ describe("setProjectDirectoryRoot", () => {
it("should update the child directory when domain name is Webpack://", () => {
const { dispatch, getState, cx } = createStore({
getSourceActorBreakableLines: async () => [],
getBreakableLines: async () => [],
});
dispatch(actions.setProjectDirectoryRoot(cx, "webpack://"));
dispatch(actions.setProjectDirectoryRoot(cx, "webpack:///app"));

Просмотреть файл

@ -12,11 +12,11 @@ import {
} from "../../utils/test-head";
const { getSelectedSource, getSourceTabs } = selectors;
import { mockCommandClient } from "./helpers/mockCommandClient";
import { sourceThreadFront as threadFront } from "./helpers/threadFront.js";
describe("closing tabs", () => {
it("closing a tab", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
const fooSource = await dispatch(
actions.newGeneratedSource(makeSource("foo.js"))
@ -29,7 +29,7 @@ describe("closing tabs", () => {
});
it("closing the inactive tab", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
const fooSource = await dispatch(
actions.newGeneratedSource(makeSource("foo.js"))
@ -45,7 +45,7 @@ describe("closing tabs", () => {
});
it("closing the only tab", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
const fooSource = await dispatch(
actions.newGeneratedSource(makeSource("foo.js"))
@ -58,7 +58,7 @@ describe("closing tabs", () => {
});
it("closing the active tab", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
const barSource = await dispatch(
@ -74,7 +74,7 @@ describe("closing tabs", () => {
});
it("closing many inactive tabs", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
await dispatch(actions.newGeneratedSource(makeSource("bar.js")));
@ -97,7 +97,7 @@ describe("closing tabs", () => {
});
it("closing many tabs including the active tab", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
await dispatch(actions.newGeneratedSource(makeSource("bar.js")));
@ -119,7 +119,7 @@ describe("closing tabs", () => {
});
it("closing all the tabs", async () => {
const { dispatch, getState, cx } = createStore(mockCommandClient);
const { dispatch, getState, cx } = createStore(threadFront);
await dispatch(actions.newGeneratedSource(makeSource("foo.js")));
await dispatch(actions.newGeneratedSource(makeSource("bar.js")));

Просмотреть файл

@ -6,7 +6,6 @@
import type { SourceId, Source, SourceLocation, Context } from "../../types";
import type { PromiseAction } from "../utils/middleware/promise";
import type { SourceBase } from "../../reducers/sources";
export type LoadSourceAction = PromiseAction<
{|
@ -25,12 +24,12 @@ export type SourceAction =
| {|
+type: "ADD_SOURCE",
+cx: Context,
+source: SourceBase,
+source: Source,
|}
| {|
+type: "ADD_SOURCES",
+cx: Context,
+sources: Array<SourceBase>,
+sources: Array<Source>,
|}
| {|
+type: "CLEAR_SOURCE_MAP_URL",
@ -77,7 +76,7 @@ export type SourceAction =
+tabs: any,
|}
| {|
type: "SET_ORIGINAL_BREAKABLE_LINES",
type: "SET_BREAKABLE_LINES",
+cx: Context,
breakableLines: number[],
sourceId: string,

Просмотреть файл

@ -4,11 +4,7 @@
// @flow
import { type PromiseAction } from "../utils/middleware/promise";
import type {
SourceActorId,
SourceActor,
} from "../../reducers/source-actors.js";
import type { SourceActor } from "../../reducers/source-actors.js";
export type SourceActorsInsertAction = {|
type: "INSERT_SOURCE_ACTORS",
@ -19,25 +15,6 @@ export type SourceActorsRemoveAction = {|
items: Array<SourceActor>,
|};
export type SourceActorBreakpointColumnsAction = PromiseAction<
{|
type: "SET_SOURCE_ACTOR_BREAKPOINT_COLUMNS",
sourceId: SourceActorId,
line: number,
|},
Array<number>
>;
export type SourceActorBreakableLinesAction = PromiseAction<
{|
type: "SET_SOURCE_ACTOR_BREAKABLE_LINES",
sourceId: SourceActorId,
|},
Array<number>
>;
export type SourceActorAction =
| SourceActorsInsertAction
| SourceActorsRemoveAction
| SourceActorBreakpointColumnsAction
| SourceActorBreakableLinesAction;
| SourceActorsRemoveAction;

Просмотреть файл

@ -6,6 +6,7 @@
import { fromPairs, toPairs } from "lodash";
import { executeSoon } from "../../../utils/DevToolsUtils";
import type { ThunkArgs } from "../../types";
type BasePromiseAction = {|
@ -29,25 +30,7 @@ export type ErrorPromiseAction = {|
+error: any,
|};
import {
pending,
rejected,
fulfilled,
type AsyncValue,
} from "../../../utils/async-value";
export function asyncActionAsValue<T>(
action: PromiseAction<mixed, T>
): AsyncValue<T> {
if (action.status === "start") {
return pending();
}
if (action.status === "error") {
return rejected(action.error);
}
return fulfilled(action.value);
}
export type PromiseAction<+Action, Value = any> =
export type PromiseAction<Action, Value = any> =
// | {| ...Action, "@@dispatch/promise": Promise<Object> |}
| {|
...BasePromiseAction,

Просмотреть файл

@ -438,38 +438,55 @@ function getMainThread() {
return currentThreadFront.actor;
}
async function getSourceActorBreakpointPositions(
{ thread, actor }: SourceActor,
range: Range
): Promise<{ [number]: number[] }> {
const sourceThreadFront = lookupThreadFront(thread);
const sourceFront = sourceThreadFront.source({ actor });
return sourceFront.getBreakpointPositionsCompressed(range);
}
async function getBreakpointPositions(
actors: Array<SourceActor>,
range: ?Range
): Promise<{ [string]: number[] }> {
const sourcePositions = {};
async function getSourceActorBreakableLines({
thread,
actor,
}: SourceActor): Promise<Array<number>> {
const sourceThreadFront = lookupThreadFront(thread);
const sourceFront = sourceThreadFront.source({ actor });
let actorLines = [];
try {
actorLines = await sourceFront.getBreakableLines();
} catch (e) {
// Handle backward compatibility
if (
e.message &&
e.message.match(/does not recognize the packet type getBreakableLines/)
) {
const pos = await sourceFront.getBreakpointPositionsCompressed();
actorLines = Object.keys(pos).map(line => Number(line));
} else if (!e.message || !e.message.match(/Connection closed/)) {
throw e;
for (const { thread, actor } of actors) {
const sourceThreadFront = lookupThreadFront(thread);
const sourceFront = sourceThreadFront.source({ actor });
const positions = await sourceFront.getBreakpointPositionsCompressed(range);
for (const line of Object.keys(positions)) {
let columns = positions[line];
const existing = sourcePositions[line];
if (existing) {
columns = [...new Set([...existing, ...columns])];
}
sourcePositions[line] = columns;
}
}
return sourcePositions;
}
return actorLines;
async function getBreakableLines(actors: Array<SourceActor>) {
let lines = [];
for (const { thread, actor } of actors) {
const sourceThreadFront = lookupThreadFront(thread);
const sourceFront = sourceThreadFront.source({ actor });
let actorLines = [];
try {
actorLines = await sourceFront.getBreakableLines();
} catch (e) {
// Handle backward compatibility
if (
e.message &&
e.message.match(/does not recognize the packet type getBreakableLines/)
) {
const pos = await sourceFront.getBreakpointPositionsCompressed();
actorLines = Object.keys(pos).map(line => Number(line));
} else if (!e.message || !e.message.match(/Connection closed/)) {
throw e;
}
}
lines = [...new Set([...lines, ...actorLines])];
}
return lines;
}
const clientCommands = {
@ -489,8 +506,8 @@ const clientCommands = {
breakOnNext,
sourceContents,
getSourceForActor,
getSourceActorBreakpointPositions,
getSourceActorBreakableLines,
getBreakpointPositions,
getBreakableLines,
hasBreakpoint,
setBreakpoint,
setXHRBreakpoint,

Просмотреть файл

@ -30,7 +30,7 @@ type Props = {
editor: SourceEditor,
hasPrettySource: boolean,
isPaused: boolean,
selectedSource: SourceWithContent,
selectedSourceWithContent: SourceWithContent,
};
class EditorMenu extends Component<Props> {
@ -47,7 +47,7 @@ class EditorMenu extends Component<Props> {
const {
cx,
editor,
selectedSource,
selectedSourceWithContent,
editorActions,
hasPrettySource,
isPaused,
@ -56,7 +56,7 @@ class EditorMenu extends Component<Props> {
const location = getSourceLocationFromMouseEvent(
editor,
selectedSource,
selectedSourceWithContent.source,
// Use a coercion, as contextMenu is optional
(event: any)
);
@ -66,7 +66,7 @@ class EditorMenu extends Component<Props> {
editorMenuItems({
cx,
editorActions,
selectedSource,
selectedSourceWithContent,
hasPrettySource,
location,
isPaused,
@ -84,7 +84,10 @@ class EditorMenu extends Component<Props> {
const mapStateToProps = (state, props) => ({
cx: getThreadContext(state),
isPaused: getIsPaused(state, getCurrentThread(state)),
hasPrettySource: !!getPrettySource(state, props.selectedSource.id),
hasPrettySource: !!getPrettySource(
state,
props.selectedSourceWithContent.source.id
),
});
const mapDispatchToProps = dispatch => ({

Просмотреть файл

@ -38,7 +38,7 @@ type CursorPosition = {
type Props = {
cx: Context,
selectedSource: ?SourceWithContent,
selectedSourceWithContent: ?SourceWithContent,
mappedSource: Source,
endPanelCollapsed: boolean,
horizontal: boolean,
@ -84,13 +84,16 @@ class SourceFooter extends PureComponent<Props, State> {
}
prettyPrintButton() {
const { cx, selectedSource, togglePrettyPrint } = this.props;
const { cx, selectedSourceWithContent, togglePrettyPrint } = this.props;
if (!selectedSource) {
if (!selectedSourceWithContent) {
return;
}
if (!selectedSource.content && selectedSource.isPrettyPrinted) {
if (
!selectedSourceWithContent.content &&
selectedSourceWithContent.source.isPrettyPrinted
) {
return (
<div className="loader" key="pretty-loader">
<AccessibleImage className="loader" />
@ -99,12 +102,13 @@ class SourceFooter extends PureComponent<Props, State> {
}
const sourceContent =
selectedSource.content && isFulfilled(selectedSource.content)
? selectedSource.content.value
selectedSourceWithContent.content &&
isFulfilled(selectedSourceWithContent.content)
? selectedSourceWithContent.content.value
: null;
if (
!shouldShowPrettyPrint(
selectedSource,
selectedSourceWithContent.source,
sourceContent || { type: "text", value: "", contentType: undefined }
)
) {
@ -112,15 +116,17 @@ class SourceFooter extends PureComponent<Props, State> {
}
const tooltip = L10N.getStr("sourceTabs.prettyPrint");
const sourceLoaded = !!selectedSource.content;
const sourceLoaded = !!selectedSourceWithContent.content;
const type = "prettyPrint";
return (
<button
onClick={() => togglePrettyPrint(cx, selectedSource.id)}
onClick={() =>
togglePrettyPrint(cx, selectedSourceWithContent.source.id)
}
className={classnames("action", type, {
active: sourceLoaded,
pretty: isPretty(selectedSource),
pretty: isPretty(selectedSourceWithContent.source),
})}
key={type}
title={tooltip}
@ -132,18 +138,19 @@ class SourceFooter extends PureComponent<Props, State> {
}
blackBoxButton() {
const { cx, selectedSource, toggleBlackBox } = this.props;
const sourceLoaded = selectedSource && selectedSource.content;
const { cx, selectedSourceWithContent, toggleBlackBox } = this.props;
const sourceLoaded =
selectedSourceWithContent && selectedSourceWithContent.content;
if (!selectedSource) {
if (!selectedSourceWithContent) {
return;
}
if (!shouldBlackbox(selectedSource)) {
if (!shouldBlackbox(selectedSourceWithContent.source)) {
return;
}
const blackboxed = selectedSource.isBlackBoxed;
const blackboxed = selectedSourceWithContent.source.isBlackBoxed;
const tooltip = blackboxed
? L10N.getStr("sourceFooter.unblackbox")
@ -153,7 +160,7 @@ class SourceFooter extends PureComponent<Props, State> {
return (
<button
onClick={() => toggleBlackBox(cx, selectedSource)}
onClick={() => toggleBlackBox(cx, selectedSourceWithContent.source)}
className={classnames("action", type, {
active: sourceLoaded,
blackboxed: blackboxed,
@ -196,10 +203,14 @@ class SourceFooter extends PureComponent<Props, State> {
cx,
mappedSource,
jumpToMappedLocation,
selectedSource,
selectedSourceWithContent,
} = this.props;
if (!mappedSource || !selectedSource || !isOriginal(selectedSource)) {
if (
!mappedSource ||
!selectedSourceWithContent ||
!isOriginal(selectedSourceWithContent.source)
) {
return null;
}
@ -210,7 +221,7 @@ class SourceFooter extends PureComponent<Props, State> {
);
const title = L10N.getFormatStr("sourceFooter.mappedSource", filename);
const mappedSourceLocation = {
sourceId: selectedSource.id,
sourceId: selectedSourceWithContent.source.id,
line: 1,
column: 1,
};
@ -231,7 +242,7 @@ class SourceFooter extends PureComponent<Props, State> {
};
renderCursorPosition() {
if (!this.props.selectedSource) {
if (!this.props.selectedSourceWithContent) {
return null;
}
@ -269,15 +280,18 @@ class SourceFooter extends PureComponent<Props, State> {
}
const mapStateToProps = state => {
const selectedSource = getSelectedSourceWithContent(state);
const selectedSourceWithContent = getSelectedSourceWithContent(state);
return {
cx: getContext(state),
selectedSource,
mappedSource: getGeneratedSource(state, selectedSource),
selectedSourceWithContent,
mappedSource: getGeneratedSource(
state,
selectedSourceWithContent && selectedSourceWithContent.source
),
prettySource: getPrettySource(
state,
selectedSource ? selectedSource.id : null
selectedSourceWithContent ? selectedSourceWithContent.source.id : null
),
endPanelCollapsed: getPaneCollapse(state, "end"),
};

Просмотреть файл

@ -28,7 +28,7 @@ type Props = {
pauseCommand: Command,
selectedFrame: Frame,
selectedLocation: SourceLocation,
selectedSource: ?SourceWithContent,
selectedSourceWithContent: ?SourceWithContent,
};
function isDebugLine(selectedFrame: Frame, selectedLocation: SourceLocation) {
@ -42,11 +42,14 @@ function isDebugLine(selectedFrame: Frame, selectedLocation: SourceLocation) {
);
}
function isDocumentReady(selectedSource: ?SourceWithContent, selectedLocation) {
function isDocumentReady(
selectedSourceWithContent: ?SourceWithContent,
selectedLocation
) {
return (
selectedLocation &&
selectedSource &&
selectedSource.content &&
selectedSourceWithContent &&
selectedSourceWithContent.content &&
hasDocument(selectedLocation.sourceId)
);
}
@ -56,8 +59,11 @@ export class HighlightLine extends Component<Props> {
previousEditorLine: ?number = null;
shouldComponentUpdate(nextProps: Props) {
const { selectedLocation, selectedSource } = nextProps;
return this.shouldSetHighlightLine(selectedLocation, selectedSource);
const { selectedLocation, selectedSourceWithContent } = nextProps;
return this.shouldSetHighlightLine(
selectedLocation,
selectedSourceWithContent
);
}
componentDidUpdate(prevProps: Props) {
@ -70,12 +76,12 @@ export class HighlightLine extends Component<Props> {
shouldSetHighlightLine(
selectedLocation: SourceLocation,
selectedSource: ?SourceWithContent
selectedSourceWithContent: ?SourceWithContent
) {
const { sourceId, line } = selectedLocation;
const editorLine = toEditorLine(sourceId, line);
if (!isDocumentReady(selectedSource, selectedLocation)) {
if (!isDocumentReady(selectedSourceWithContent, selectedLocation)) {
return false;
}
@ -91,7 +97,7 @@ export class HighlightLine extends Component<Props> {
pauseCommand,
selectedLocation,
selectedFrame,
selectedSource,
selectedSourceWithContent,
} = this.props;
if (pauseCommand) {
this.isStepping = true;
@ -101,20 +107,26 @@ export class HighlightLine extends Component<Props> {
if (prevProps) {
this.clearHighlightLine(
prevProps.selectedLocation,
prevProps.selectedSource
prevProps.selectedSourceWithContent
);
}
this.setHighlightLine(selectedLocation, selectedFrame, selectedSource);
this.setHighlightLine(
selectedLocation,
selectedFrame,
selectedSourceWithContent
);
endOperation();
}
setHighlightLine(
selectedLocation: SourceLocation,
selectedFrame: Frame,
selectedSource: ?SourceWithContent
selectedSourceWithContent: ?SourceWithContent
) {
const { sourceId, line } = selectedLocation;
if (!this.shouldSetHighlightLine(selectedLocation, selectedSource)) {
if (
!this.shouldSetHighlightLine(selectedLocation, selectedSourceWithContent)
) {
return;
}
@ -154,9 +166,9 @@ export class HighlightLine extends Component<Props> {
clearHighlightLine(
selectedLocation: SourceLocation,
selectedSource: ?SourceWithContent
selectedSourceWithContent: ?SourceWithContent
) {
if (!isDocumentReady(selectedSource, selectedLocation)) {
if (!isDocumentReady(selectedSourceWithContent, selectedLocation)) {
return;
}
@ -175,5 +187,5 @@ export default connect(state => ({
pauseCommand: getPauseCommand(state, getCurrentThread(state)),
selectedFrame: getVisibleSelectedFrame(state),
selectedLocation: getSelectedLocation(state),
selectedSource: getSelectedSourceWithContent(state),
selectedSourceWithContent: getSelectedSourceWithContent(state),
}))(HighlightLine);

Просмотреть файл

@ -95,7 +95,7 @@ const cssVars = {
export type Props = {
cx: ThreadContext,
selectedLocation: ?SourceLocation,
selectedSource: ?SourceWithContent,
selectedSourceWithContent: ?SourceWithContent,
searchOn: boolean,
startPanelSize: number,
endPanelSize: number,
@ -139,7 +139,7 @@ class Editor extends PureComponent<Props, State> {
componentWillReceiveProps(nextProps: Props) {
let editor = this.state.editor;
if (!this.state.editor && nextProps.selectedSource) {
if (!this.state.editor && nextProps.selectedSourceWithContent) {
editor = this.setupEditor();
}
@ -149,7 +149,10 @@ class Editor extends PureComponent<Props, State> {
this.scrollToLocation(nextProps, editor);
endOperation();
if (this.props.selectedSource != nextProps.selectedSource) {
if (
this.props.selectedSourceWithContent !=
nextProps.selectedSourceWithContent
) {
this.props.updateViewport();
resizeBreakpointGutter(editor.codeMirror);
resizeToggleButton(editor.codeMirror);
@ -227,11 +230,11 @@ class Editor extends PureComponent<Props, State> {
}
onClosePress = (key, e: KeyboardEvent) => {
const { cx, selectedSource } = this.props;
if (selectedSource) {
const { cx, selectedSourceWithContent } = this.props;
if (selectedSourceWithContent) {
e.preventDefault();
e.stopPropagation();
this.props.closeTab(cx, selectedSource);
this.props.closeTab(cx, selectedSourceWithContent.source);
}
};
@ -251,13 +254,13 @@ class Editor extends PureComponent<Props, State> {
getCurrentLine() {
const { codeMirror } = this.state.editor;
const { selectedSource } = this.props;
if (!selectedSource) {
const { selectedSourceWithContent } = this.props;
if (!selectedSourceWithContent) {
return;
}
const line = getCursorLine(codeMirror);
return toSourceLine(selectedSource.id, line);
return toSourceLine(selectedSourceWithContent.source.id, line);
}
onToggleBreakpoint = (key, e: KeyboardEvent) => {
@ -328,7 +331,7 @@ class Editor extends PureComponent<Props, State> {
const {
cx,
selectedSource,
selectedSourceWithContent,
breakpointActions,
editorActions,
isPaused,
@ -336,7 +339,7 @@ class Editor extends PureComponent<Props, State> {
closeConditionalPanel,
} = this.props;
const { editor } = this.state;
if (!selectedSource || !editor) {
if (!selectedSourceWithContent || !editor) {
return;
}
@ -346,7 +349,7 @@ class Editor extends PureComponent<Props, State> {
}
const target: Element = (event.target: any);
const { id: sourceId } = selectedSource;
const { id: sourceId } = selectedSourceWithContent.source;
const line = lineAtHeight(editor, sourceId, event);
if (typeof line != "number") {
@ -382,7 +385,7 @@ class Editor extends PureComponent<Props, State> {
) => {
const {
cx,
selectedSource,
selectedSourceWithContent,
conditionalPanelLocation,
closeConditionalPanel,
addBreakpointAtLine,
@ -391,13 +394,20 @@ class Editor extends PureComponent<Props, State> {
} = this.props;
// ignore right clicks in the gutter
if ((ev.ctrlKey && ev.button === 0) || ev.button === 2 || !selectedSource) {
if (
(ev.ctrlKey && ev.button === 0) ||
ev.button === 2 ||
!selectedSourceWithContent
) {
return;
}
// if user clicks gutter to set breakpoint on blackboxed source, un-blackbox the source.
if (selectedSource && selectedSource.isBlackBoxed) {
toggleBlackBox(cx, selectedSource);
if (
selectedSourceWithContent &&
selectedSourceWithContent.source.isBlackBoxed
) {
toggleBlackBox(cx, selectedSourceWithContent.source);
}
if (conditionalPanelLocation) {
@ -408,7 +418,7 @@ class Editor extends PureComponent<Props, State> {
return;
}
const sourceLine = toSourceLine(selectedSource.id, line);
const sourceLine = toSourceLine(selectedSourceWithContent.source.id, line);
if (typeof sourceLine !== "number") {
return;
}
@ -425,12 +435,12 @@ class Editor extends PureComponent<Props, State> {
};
onClick(e: MouseEvent) {
const { cx, selectedSource, jumpToMappedLocation } = this.props;
const { cx, selectedSourceWithContent, jumpToMappedLocation } = this.props;
if (selectedSource && e.metaKey && e.altKey) {
if (selectedSourceWithContent && e.metaKey && e.altKey) {
const sourceLocation = getSourceLocationFromMouseEvent(
this.state.editor,
selectedSource,
selectedSourceWithContent.source,
e
);
jumpToMappedLocation(cx, sourceLocation);
@ -442,42 +452,42 @@ class Editor extends PureComponent<Props, State> {
conditionalPanelLocation,
closeConditionalPanel,
openConditionalPanel,
selectedSource,
selectedSourceWithContent,
} = this.props;
if (conditionalPanelLocation) {
return closeConditionalPanel();
}
if (!selectedSource) {
if (!selectedSourceWithContent) {
return;
}
return openConditionalPanel(
{
line: line,
sourceId: selectedSource.id,
sourceUrl: selectedSource.url,
sourceId: selectedSourceWithContent.source.id,
sourceUrl: selectedSourceWithContent.source.url,
},
log
);
};
shouldScrollToLocation(nextProps, editor) {
const { selectedLocation, selectedSource } = this.props;
const { selectedLocation, selectedSourceWithContent } = this.props;
if (
!editor ||
!nextProps.selectedSource ||
!nextProps.selectedSourceWithContent ||
!nextProps.selectedLocation ||
!nextProps.selectedLocation.line ||
!nextProps.selectedSource.content
!nextProps.selectedSourceWithContent.content
) {
return false;
}
const isFirstLoad =
(!selectedSource || !selectedSource.content) &&
nextProps.selectedSource.content;
(!selectedSourceWithContent || !selectedSourceWithContent.content) &&
nextProps.selectedSourceWithContent.content;
const locationChanged = selectedLocation !== nextProps.selectedLocation;
const symbolsChanged = nextProps.symbols != this.props.symbols;
@ -485,13 +495,16 @@ class Editor extends PureComponent<Props, State> {
}
scrollToLocation(nextProps, editor) {
const { selectedLocation, selectedSource } = nextProps;
const { selectedLocation, selectedSourceWithContent } = nextProps;
if (selectedLocation && this.shouldScrollToLocation(nextProps, editor)) {
let { line, column } = toEditorPosition(selectedLocation);
if (selectedSource && hasDocument(selectedSource.id)) {
const doc = getDocument(selectedSource.id);
if (
selectedSourceWithContent &&
hasDocument(selectedSourceWithContent.source.id)
) {
const doc = getDocument(selectedSourceWithContent.source.id);
const lineText: ?string = doc.getLine(line);
column = Math.max(column, getIndentation(lineText));
}
@ -514,23 +527,23 @@ class Editor extends PureComponent<Props, State> {
}
setText(props, editor) {
const { selectedSource, symbols } = props;
const { selectedSourceWithContent, symbols } = props;
if (!editor) {
return;
}
// check if we previously had a selected source
if (!selectedSource) {
if (!selectedSourceWithContent) {
return this.clearEditor();
}
if (!selectedSource.content) {
if (!selectedSourceWithContent.content) {
return showLoading(editor);
}
if (selectedSource.content.state === "rejected") {
let { value } = selectedSource.content;
if (selectedSourceWithContent.content.state === "rejected") {
let { value } = selectedSourceWithContent.content;
if (typeof value !== "string") {
value = "Unexpected source error";
}
@ -540,8 +553,8 @@ class Editor extends PureComponent<Props, State> {
return showSourceText(
editor,
selectedSource,
selectedSource.content.value,
selectedSourceWithContent.source,
selectedSourceWithContent.content.value,
symbols
);
}
@ -581,13 +594,17 @@ class Editor extends PureComponent<Props, State> {
renderItems() {
const {
cx,
selectedSource,
selectedSourceWithContent,
conditionalPanelLocation,
isPaused,
} = this.props;
const { editor, contextMenu } = this.state;
if (!selectedSource || !editor || !getDocument(selectedSource.id)) {
if (
!selectedSourceWithContent ||
!editor ||
!getDocument(selectedSourceWithContent.source.id)
) {
return null;
}
@ -604,7 +621,7 @@ class Editor extends PureComponent<Props, State> {
editor={editor}
contextMenu={contextMenu}
clearContextMenu={this.clearContextMenu}
selectedSource={selectedSource}
selectedSourceWithContent={selectedSourceWithContent}
/>
}
{conditionalPanelLocation ? <ConditionalPanel editor={editor} /> : null}
@ -612,7 +629,10 @@ class Editor extends PureComponent<Props, State> {
<ColumnBreakpoints editor={editor} />
) : null}
{isPaused && features.inlinePreview ? (
<InlinePreviews editor={editor} selectedSource={selectedSource} />
<InlinePreviews
editor={editor}
selectedSource={selectedSourceWithContent.source}
/>
) : null}
</div>
);
@ -621,7 +641,7 @@ class Editor extends PureComponent<Props, State> {
renderSearchBar() {
const { editor } = this.state;
if (!this.props.selectedSource) {
if (!this.props.selectedSourceWithContent) {
return null;
}
@ -629,11 +649,13 @@ class Editor extends PureComponent<Props, State> {
}
render() {
const { selectedSource, skipPausing } = this.props;
const { selectedSourceWithContent, skipPausing } = this.props;
return (
<div
className={classnames("editor-wrapper", {
blackboxed: selectedSource && selectedSource.isBlackBoxed,
blackboxed:
selectedSourceWithContent &&
selectedSourceWithContent.source.isBlackBoxed,
"skip-pausing": skipPausing,
})}
ref={c => (this.$editorWrapper = c)}
@ -654,15 +676,18 @@ Editor.contextTypes = {
};
const mapStateToProps = state => {
const selectedSource = getSelectedSourceWithContent(state);
const selectedSourceWithContent = getSelectedSourceWithContent(state);
return {
cx: getThreadContext(state),
selectedLocation: getSelectedLocation(state),
selectedSource,
selectedSourceWithContent,
searchOn: getActiveSearch(state) === "file",
conditionalPanelLocation: getConditionalPanelLocation(state),
symbols: getSymbols(state, selectedSource),
symbols: getSymbols(
state,
selectedSourceWithContent ? selectedSourceWithContent.source : null
),
isPaused: getIsPaused(state, getCurrentThread(state)),
skipPausing: getSkipPausing(state),
};

Просмотреть файл

@ -168,7 +168,7 @@ const downloadFileItem = (
export function editorMenuItems({
cx,
editorActions,
selectedSource,
selectedSourceWithContent,
location,
selectionText,
hasPrettySource,
@ -177,7 +177,7 @@ export function editorMenuItems({
}: {
cx: ThreadContext,
editorActions: EditorItemActions,
selectedSource: SourceWithContent,
selectedSourceWithContent: SourceWithContent,
location: SourceLocation,
selectionText: string,
hasPrettySource: boolean,
@ -185,11 +185,7 @@ export function editorMenuItems({
isPaused: boolean,
}) {
const items = [];
const content =
selectedSource.content && isFulfilled(selectedSource.content)
? selectedSource.content.value
: null;
const { source: selectedSource, content } = selectedSourceWithContent;
items.push(
jumpToMappedLocationItem(
@ -201,15 +197,17 @@ export function editorMenuItems({
),
continueToHereItem(cx, location, isPaused, editorActions),
{ type: "separator" },
...(content ? [copyToClipboardItem(content, editorActions)] : []),
...(content && isFulfilled(content)
? [copyToClipboardItem(content.value, editorActions)]
: []),
...(!selectedSource.isWasm
? [
copySourceItem(selectedSource, selectionText, editorActions),
copySourceUri2Item(selectedSource, editorActions),
]
: []),
...(content
? [downloadFileItem(selectedSource, content, editorActions)]
...(content && isFulfilled(content)
? [downloadFileItem(selectedSource, content.value, editorActions)]
: []),
{ type: "separator" },
showSourceMenuItem(cx, selectedSource, editorActions),

Просмотреть файл

@ -34,7 +34,7 @@ function generateDefaults(editor, overrides) {
},
frame: null,
source: ({
...createSourceObject("foo"),
source: createSourceObject("foo"),
content: null,
}: SourceWithContent),
...overrides,
@ -57,7 +57,7 @@ function render(overrides = {}) {
const props = generateDefaults(editor, overrides);
const doc = createMockDocument(clear);
setDocument(props.source.id, doc);
setDocument(props.source.source.id, doc);
// $FlowIgnore
const component = shallow(<DebugLine.WrappedComponent {...props} />, {
@ -71,7 +71,7 @@ describe("DebugLine Component", () => {
it("should show a new debug line", async () => {
const { component, props, doc } = render({
source: {
...createSourceObject("foo"),
source: createSourceObject("foo"),
content: asyncValue.fulfilled({
type: "text",
value: "",
@ -94,7 +94,7 @@ describe("DebugLine Component", () => {
it("should replace the first debug line", async () => {
const { props, component, clear, doc } = render({
source: {
...createSourceObject("foo"),
source: createSourceObject("foo"),
content: asyncValue.fulfilled({
type: "text",
value: "",

Просмотреть файл

@ -7,7 +7,7 @@
import React from "react";
import { shallow } from "enzyme";
import Editor from "../index";
import type { Source, SourceWithContent, SourceBase } from "../../../types";
import type { Source, SourceWithContent } from "../../../types";
import { getDocument } from "../../../utils/editor/source-documents";
import * as asyncValue from "../../../utils/async-value";
@ -74,7 +74,7 @@ function createMockSourceWithContent(
...otherOverrides
} = overrides;
const source: SourceBase = ({
const source: Source = ({
id: "foo",
url: "foo",
...otherOverrides,
@ -95,7 +95,7 @@ function createMockSourceWithContent(
}
return {
...source,
source,
content,
};
}
@ -128,7 +128,7 @@ describe("Editor", () => {
const { component, mockEditor } = render();
await component.setState({ editor: mockEditor });
component.setProps({
selectedSource: {
selectedSourceWithContent: {
source: { loadedState: "loading" },
content: null,
},
@ -148,7 +148,7 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
loadedState: "loaded",
}),
selectedLocation: { sourceId: "foo", line: 3, column: 1 },
@ -166,7 +166,7 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
loadedState: "loaded",
text: undefined,
error: "error text",
@ -185,7 +185,7 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
loadedState: "loaded",
isWasm: true,
text: undefined,
@ -207,7 +207,7 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
loadedState: "loaded",
}),
selectedLocation: { sourceId: "foo", line: 3, column: 1 },
@ -216,7 +216,7 @@ describe("Editor", () => {
// navigate to a new source that is still loading
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
id: "bar",
loadedState: "loading",
}),
@ -237,17 +237,17 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
const selectedSource = createMockSourceWithContent({
const selectedSourceWithContent = createMockSourceWithContent({
loadedState: "loaded",
contentType: "javascript",
});
await component.setProps({ ...props, selectedSource });
await component.setProps({ ...props, selectedSourceWithContent });
const symbols = { hasJsx: true };
await component.setProps({
...props,
selectedSource,
selectedSourceWithContent,
symbols,
});
@ -262,29 +262,31 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
const selectedSource = createMockSourceWithContent({
const selectedSourceWithContent = createMockSourceWithContent({
loadedState: "loaded",
contentType: "javascript",
});
await component.setProps({ ...props, selectedSource });
await component.setProps({ ...props, selectedSourceWithContent });
// symbols are parsed
const symbols = { hasJsx: true };
await component.setProps({
...props,
selectedSource,
selectedSourceWithContent,
symbols,
});
// selectedLocation changes e.g. pausing/stepping
mockEditor.codeMirror.doc = getDocument(selectedSource.id);
mockEditor.codeMirror.doc = getDocument(
selectedSourceWithContent.source.id
);
mockEditor.codeMirror.getOption = () => ({ name: "jsx" });
const selectedLocation = { sourceId: "foo", line: 4, column: 1 };
await component.setProps({
...props,
selectedSource,
selectedSourceWithContent,
symbols,
selectedLocation,
});
@ -303,7 +305,7 @@ describe("Editor", () => {
await component.setState({ editor: mockEditor });
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
loadedState: "loading",
}),
selectedLocation: { sourceId: "foo", line: 1, column: 1 },
@ -312,7 +314,7 @@ describe("Editor", () => {
// navigate to a new source that is still loading
await component.setProps({
...props,
selectedSource: createMockSourceWithContent({
selectedSourceWithContent: createMockSourceWithContent({
loadedState: "loaded",
}),
selectedLocation: { sourceId: "foo", line: 1, column: 1 },

Просмотреть файл

@ -29,8 +29,8 @@ function generateDefaults(overrides) {
},
},
endPanelCollapsed: false,
selectedSource: {
...createSourceObject("foo"),
selectedSourceWithContent: {
source: createSourceObject("foo"),
content: null,
},
...overrides,
@ -42,7 +42,7 @@ function render(overrides = {}, position = { line: 0, column: 0 }) {
const props = generateDefaults(overrides);
const doc = createMockDocument(clear, position);
setDocument(props.selectedSource.id, doc);
setDocument(props.selectedSourceWithContent.source.id, doc);
// $FlowIgnore
const component = shallow(<SourceFooter.WrappedComponent {...props} />, {

Просмотреть файл

@ -263,12 +263,14 @@ export class Outline extends Component<Props, State> {
const mapStateToProps = state => {
const selectedSource = getSelectedSourceWithContent(state);
const symbols = selectedSource ? getSymbols(state, selectedSource) : null;
const symbols = selectedSource
? getSymbols(state, selectedSource.source)
: null;
return {
cx: getContext(state),
symbols,
selectedSource: (selectedSource: ?Source),
selectedSource: selectedSource && selectedSource.source,
selectedLocation: getSelectedLocation(state),
getFunctionText: line => {
if (selectedSource) {

Просмотреть файл

@ -206,8 +206,8 @@ describe("Frames", () => {
];
const sources: SourceResourceState = insertResources(createInitial(), [
{ ...source1, content: null },
{ ...source2, content: null },
source1,
source2,
]);
const processedFrames = formatCallStackFrames(frames, sources, source1);

Просмотреть файл

@ -6,35 +6,20 @@
import type { Action } from "../actions/types";
import type { SourceId, ThreadId } from "../types";
import {
asSettled,
type AsyncValue,
type SettledValue,
} from "../utils/async-value";
import {
createInitial,
insertResources,
updateResources,
removeResources,
hasResource,
getResource,
getMappedResource,
makeWeakQuery,
makeIdQuery,
makeReduceAllQuery,
type Resource,
type ResourceState,
type WeakQuery,
type IdQuery,
type ReduceAllQuery,
} from "../utils/resource";
import { asyncActionAsValue } from "../actions/utils/middleware/promise";
import type {
SourceActorBreakpointColumnsAction,
SourceActorBreakableLinesAction,
} from "../actions/types/SourceActorAction";
export opaque type SourceActorId: string = string;
export type SourceActor = {|
+id: SourceActorId,
@ -62,12 +47,6 @@ export type SourceActor = {|
type SourceActorResource = Resource<{
...SourceActor,
// The list of breakpoint positions on each line of the file.
breakpointPositions: Map<number, AsyncValue<Array<number>>>,
// The list of lines that contain breakpoints.
breakableLines: AsyncValue<Array<number>> | null,
}>;
export type SourceActorsState = ResourceState<SourceActorResource>;
export type SourceActorOuterState = { sourceActors: SourceActorsState };
@ -81,14 +60,7 @@ export default function update(
switch (action.type) {
case "INSERT_SOURCE_ACTORS": {
const { items } = action;
state = insertResources(
state,
items.map(item => ({
...item,
breakpointPositions: new Map(),
breakableLines: null,
}))
);
state = insertResources(state, items);
break;
}
case "REMOVE_SOURCE_ACTORS": {
@ -101,58 +73,13 @@ export default function update(
state = initial;
break;
}
case "SET_SOURCE_ACTOR_BREAKPOINT_COLUMNS":
state = updateBreakpointColumns(state, action);
break;
case "SET_SOURCE_ACTOR_BREAKABLE_LINES":
state = updateBreakableLines(state, action);
break;
}
return state;
}
function updateBreakpointColumns(
state: SourceActorsState,
action: SourceActorBreakpointColumnsAction
): SourceActorsState {
const { sourceId, line } = action;
const value = asyncActionAsValue(action);
if (!hasResource(state, sourceId)) {
return state;
}
const breakpointPositions = new Map(
getResource(state, sourceId).breakpointPositions
);
breakpointPositions.set(line, value);
return updateResources(state, [{ id: sourceId, breakpointPositions }]);
}
function updateBreakableLines(
state: SourceActorsState,
action: SourceActorBreakableLinesAction
): SourceActorsState {
const value = asyncActionAsValue(action);
const { sourceId } = action;
if (!hasResource(state, sourceId)) {
return state;
}
return updateResources(state, [{ id: sourceId, breakableLines: value }]);
}
export function resourceAsSourceActor({
breakpointPositions,
breakableLines,
...sourceActor
}: SourceActorResource): SourceActor {
return sourceActor;
export function resourceAsSourceActor(r: SourceActorResource): SourceActor {
return r;
}
// Because we are using an opaque type for our source actor IDs, these
@ -174,7 +101,7 @@ export function getSourceActor(
state: SourceActorOuterState,
id: SourceActorId
): SourceActor {
return getMappedResource(state.sourceActors, id, resourceAsSourceActor);
return getResource(state.sourceActors, id);
}
/**
@ -239,40 +166,3 @@ export function getThreadsBySource(
): { [SourceId]: Array<ThreadId> } {
return queryThreadsBySourceObject(state.sourceActors);
}
export function getSourceActorBreakableLines(
state: SourceActorOuterState,
id: SourceActorId
): SettledValue<Array<number>> | null {
const { breakableLines } = getResource(state.sourceActors, id);
return asSettled(breakableLines);
}
export function getSourceActorBreakpointColumns(
state: SourceActorOuterState,
id: SourceActorId,
line: number
): SettledValue<Array<number>> | null {
const { breakpointPositions } = getResource(state.sourceActors, id);
return asSettled(breakpointPositions.get(line) || null);
}
export const getBreakableLinesForSourceActors: WeakQuery<
SourceActorResource,
Array<SourceActorId>,
Array<number>
> = makeWeakQuery({
filter: (state, ids) => ids,
map: ({ breakableLines }) => breakableLines,
reduce: items =>
Array.from(
items.reduce((acc, item) => {
if (item && item.state === "fulfilled") {
acc = acc.concat(item.value);
}
return acc;
}, [])
),
});

Просмотреть файл

@ -24,9 +24,7 @@ import {
updateResources,
hasResource,
getResource,
getMappedResource,
getResourceIds,
memoizeResourceShallow,
makeReduceQuery,
makeReduceAllQuery,
makeMapWithArgs,
@ -47,7 +45,6 @@ import {
getSourceActor,
getSourceActors,
getThreadsBySource,
getBreakableLinesForSourceActors,
type SourceActorId,
type SourceActorOuterState,
} from "./source-actors";
@ -69,6 +66,9 @@ import type { DebuggeeState } from "./debuggee";
import { uniq } from "lodash";
export type SourcesMap = { [SourceId]: Source };
type SourcesContentMap = {
[SourceId]: AsyncValue<SourceContent> | null,
};
export type SourcesMapByThread = { [ThreadId]: SourcesMap };
export type BreakpointPositionsMap = { [SourceId]: BreakpointPositions };
@ -77,23 +77,8 @@ type SourceActorMap = { [SourceId]: Array<SourceActorId> };
type UrlsMap = { [string]: SourceId[] };
type PlainUrlsMap = { [string]: string[] };
export type SourceBase = {|
+id: SourceId,
+url: string,
+sourceMapURL?: string,
+isBlackBoxed: boolean,
+isPrettyPrinted: boolean,
+relativeUrl: string,
+introductionUrl: ?string,
+introductionType: ?string,
+extensionName: ?string,
+isExtension: boolean,
+isWasm: boolean,
|};
type SourceResource = Resource<{
...SourceBase,
content: AsyncValue<SourceContent> | null,
...Source,
}>;
export type SourceResourceState = ResourceState<SourceResource>;
@ -103,6 +88,8 @@ export type SourcesState = {
// All known sources.
sources: SourceResourceState,
content: SourcesContentMap,
breakpointPositions: BreakpointPositionsMap,
breakableLines: { [SourceId]: Array<number> },
@ -217,7 +204,7 @@ function update(
case "SET_PROJECT_DIRECTORY_ROOT":
return updateProjectDirectoryRoot(state, action.url);
case "SET_ORIGINAL_BREAKABLE_LINES": {
case "SET_BREAKABLE_LINES": {
const { breakableLines, sourceId } = action;
return {
...state,
@ -253,45 +240,36 @@ function update(
return state;
}
const resourceAsSourceBase = memoizeResourceShallow(
({ content, ...source }: SourceResource): SourceBase => source
);
const resourceAsSourceWithContent = memoizeResourceShallow(
({ content, ...source }: SourceResource): SourceWithContent => ({
...source,
content: asyncValue.asSettled(content),
})
);
function resourceAsSource(r: SourceResource): Source {
return r;
}
/*
* Add sources to the sources store
* - Add the source to the sources store
* - Add the source URL to the urls map
*/
function addSources(state: SourcesState, sources: SourceBase[]): SourcesState {
function addSources(state: SourcesState, sources: Source[]): SourcesState {
state = {
...state,
content: { ...state.content },
urls: { ...state.urls },
plainUrls: { ...state.plainUrls },
};
state.sources = insertResources(
state.sources,
sources.map(source => ({
...source,
content: null,
}))
);
state.sources = insertResources(state.sources, sources);
for (const source of sources) {
// 1. Update the source url map
// 1. Add the source to the sources map
state.content[source.id] = null;
// 2. Update the source url map
const existing = state.urls[source.url] || [];
if (!existing.includes(source.id)) {
state.urls[source.url] = [...existing, source.id];
}
// 2. Update the plain url map
// 3. Update the plain url map
if (source.url) {
const plainUrl = getPlainUrl(source.url);
const existingPlainUrls = state.plainUrls[plainUrl] || [];
@ -375,7 +353,7 @@ function updateProjectDirectoryRoot(state: SourcesState, root: string) {
function updateRootRelativeValues(
state: SourcesState,
sources?: $ReadOnlyArray<Source>
sources?: Array<Source>
) {
const ids = sources
? sources.map(source => source.id)
@ -411,7 +389,7 @@ function updateLoadedState(
// If there was a navigation between the time the action was started and
// completed, we don't want to update the store.
if (action.epoch !== state.epoch || !hasResource(state.sources, sourceId)) {
if (action.epoch !== state.epoch || !(sourceId in state.content)) {
return state;
}
@ -435,12 +413,10 @@ function updateLoadedState(
return {
...state,
sources: updateResources(state.sources, [
{
id: sourceId,
content,
},
]),
content: {
...state.content,
[sourceId]: content,
},
};
}
@ -538,9 +514,7 @@ export function getSourceInSources(
sources: SourceResourceState,
id: string
): ?Source {
return hasResource(sources, id)
? getMappedResource(sources, id, resourceAsSourceBase)
: null;
return hasResource(sources, id) ? getResource(sources, id) : null;
}
export function getSource(state: OuterState, id: SourceId): ?Source {
@ -574,9 +548,7 @@ export function getSourcesByURLInSources(
if (!url || !urls[url]) {
return [];
}
return urls[url].map(id =>
getMappedResource(sources, id, resourceAsSourceBase)
);
return urls[url].map(id => getResource(sources, id));
}
export function getSourcesByURL(state: OuterState, url: string): Source[] {
@ -695,7 +667,7 @@ export function getHasSiblingOfSameName(state: OuterState, source: ?Source) {
const querySourceList: ReduceAllQuery<
SourceResource,
Array<Source>
> = makeReduceAllQuery(resourceAsSourceBase, sources => sources.slice());
> = makeReduceAllQuery(resourceAsSource, sources => sources.slice());
export function getSources(state: OuterState): SourceResourceState {
return state.sources.sources;
@ -762,15 +734,17 @@ type GSSWC = Selector<?SourceWithContent>;
export const getSelectedSourceWithContent: GSSWC = createSelector(
getSelectedLocation,
getSources,
state => state.sources.content,
(
selectedLocation: ?SourceLocation,
sources: SourceResourceState
sources: SourceResourceState,
content: SourcesContentMap
): SourceWithContent | null => {
const source =
selectedLocation &&
getSourceInSources(sources, selectedLocation.sourceId);
return source
? getMappedResource(sources, source.id, resourceAsSourceWithContent)
? getSourceWithContentInner(sources, content, source.id)
: null;
}
);
@ -778,18 +752,49 @@ export function getSourceWithContent(
state: OuterState,
id: SourceId
): SourceWithContent {
return getMappedResource(
return getSourceWithContentInner(
state.sources.sources,
id,
resourceAsSourceWithContent
state.sources.content,
id
);
}
export function getSourceContent(
state: OuterState,
id: SourceId
): SettledValue<SourceContent> | null {
const { content } = getResource(state.sources.sources, id);
return asyncValue.asSettled(content);
// Assert the resource exists.
getResource(state.sources.sources, id);
const content = state.sources.content[id];
if (!content || content.state === "pending") {
return null;
}
return content;
}
const contentLookup: WeakMap<Source, SourceWithContent> = new WeakMap();
function getSourceWithContentInner(
sources: SourceResourceState,
content: SourcesContentMap,
id: SourceId
): SourceWithContent {
const source = getResource(sources, id);
let contentValue = content[source.id];
let result = contentLookup.get(source);
if (!result || result.content !== contentValue) {
if (contentValue && contentValue.state === "pending") {
contentValue = null;
}
result = {
source,
content: contentValue,
};
contentLookup.set(source, result);
}
return result;
}
export function getSelectedSourceId(state: OuterState) {
@ -965,40 +970,24 @@ export function getBreakpointPositionsForLocation(
return findPosition(positions, location);
}
export function getBreakableLines(
state: OuterState & SourceActorOuterState,
sourceId: string
): ?Array<number> {
export function getBreakableLines(state: OuterState, sourceId: string) {
if (!sourceId) {
return null;
}
const source = getSource(state, sourceId);
if (!source) {
return null;
}
if (isOriginalSource(source)) {
return state.sources.breakableLines[sourceId];
}
// We pull generated file breakable lines directly from the source actors
// so that breakable lines can be added as new source actors on HTML loads.
return getBreakableLinesForSourceActors(
state.sourceActors,
state.sources.actors[sourceId]
);
return state.sources.breakableLines[sourceId];
}
export const getSelectedBreakableLines: Selector<Set<number>> = createSelector(
state => {
const sourceId = getSelectedSourceId(state);
return sourceId && getBreakableLines(state, sourceId);
return sourceId && state.sources.breakableLines[sourceId];
},
breakableLines => new Set(breakableLines || [])
);
export function isSourceLoadingOrLoaded(state: OuterState, sourceId: string) {
const { content } = getResource(state.sources.sources, sourceId);
const content = state.sources.content[sourceId];
return content !== null;
}

Просмотреть файл

@ -10,7 +10,7 @@ declare var expect: (value: any) => any;
import update, { initialSourcesState, getDisplayedSources } from "../sources";
import { initialDebuggeeState } from "../debuggee";
import updateSourceActors from "../source-actors";
import type { SourceActor } from "../../types";
import type { Source, SourceActor } from "../../types";
import { prefs } from "../../utils/prefs";
import { makeMockSource, mockcx } from "../../utils/test-mockup";
import { getResourceIds } from "../../utils/resource";
@ -82,7 +82,7 @@ describe("sources selectors", () => {
sources: update(state, {
type: "ADD_SOURCES",
cx: mockcx,
sources: mockedSources,
sources: ((mockedSources: any): Source[]),
}),
sourceActors: undefined,
};
@ -106,7 +106,7 @@ describe("sources selectors", () => {
sources: update(state, {
type: "ADD_SOURCES",
cx: mockcx,
sources: mockedSources,
sources: ((mockedSources: any): Source[]),
}),
sourceActors: undefined,
};

Просмотреть файл

@ -148,12 +148,17 @@ export function getColumnBreakpoints(
positions: BreakpointPosition[],
breakpoints: ?(Breakpoint[]),
viewport: ?Range,
selectedSource: ?SourceWithContent
selectedSourceWithContent: ?SourceWithContent
) {
if (!positions || !selectedSource) {
if (!positions || !selectedSourceWithContent) {
return [];
}
const {
source: selectedSource,
content: selectedContent,
} = selectedSourceWithContent;
// We only want to show a column breakpoint if several conditions are matched
// - it is the first breakpoint to appear at an the original location
// - the position is in the current viewport
@ -162,7 +167,7 @@ export function getColumnBreakpoints(
const breakpointMap = groupBreakpoints(breakpoints, selectedSource);
positions = filterByLineCount(positions, selectedSource);
positions = filterVisible(positions, selectedSource, viewport);
positions = filterInLine(positions, selectedSource, selectedSource.content);
positions = filterInLine(positions, selectedSource, selectedContent);
positions = filterByBreakpoints(positions, selectedSource, breakpointMap);
return formatPositions(positions, selectedSource, breakpointMap);

Просмотреть файл

@ -7,9 +7,8 @@
import type { SettledValue, FulfilledValue } from "./utils/async-value";
import type { SourcePayload } from "./client/firefox/types";
import type { SourceActorId, SourceActor } from "./reducers/source-actors";
import type { SourceBase } from "./reducers/sources";
export type { SourceActorId, SourceActor, SourceBase };
export type { SourceActorId, SourceActor };
export type SearchModifiers = {
caseSensitive: boolean,
@ -382,14 +381,14 @@ export type WasmSourceContent = {|
|};
export type SourceContent = TextSourceContent | WasmSourceContent;
export type SourceWithContent = $ReadOnly<{
...SourceBase,
export type SourceWithContent = {|
source: Source,
+content: SettledValue<SourceContent> | null,
}>;
export type SourceWithContentAndType<+Content: SourceContent> = $ReadOnly<{
...SourceBase,
|};
export type SourceWithContentAndType<+Content: SourceContent> = {|
source: Source,
+content: FulfilledValue<Content>,
}>;
|};
/**
* Source
@ -398,7 +397,7 @@ export type SourceWithContentAndType<+Content: SourceContent> = $ReadOnly<{
* @static
*/
export type Source = {
export type Source = {|
+id: SourceId,
+url: string,
+sourceMapURL?: string,
@ -410,7 +409,7 @@ export type Source = {
+extensionName: ?string,
+isExtension: boolean,
+isWasm: boolean,
};
|};
/**
* Script

Просмотреть файл

@ -28,12 +28,6 @@ export function rejected(value: mixed): RejectedValue {
return { state: "rejected", value };
}
export function asSettled<T>(
value: AsyncValue<T> | null
): SettledValue<T> | null {
return value && value.state !== "pending" ? value : null;
}
export function isPending(value: AsyncValue<mixed>): boolean %checks {
return value.state === "pending";
}

Просмотреть файл

@ -13,7 +13,9 @@ import { getSymbols } from "../../../workers/parser/getSymbols";
import cases from "jest-in-case";
async function setup({ file, location, functionName, original }) {
const source = original ? populateOriginalSource(file) : populateSource(file);
const { source } = original
? populateOriginalSource(file)
: populateSource(file);
const symbols = getSymbols(source.id);

Просмотреть файл

@ -26,7 +26,7 @@ import { makeMockSource, makeMockSourceAndContent } from "../../test-mockup";
describe("shouldShowPrettyPrint", () => {
it("shows pretty print for a source", () => {
const { content, ...source } = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"http://example.com/index.js",
"test-id-123",
"text/javascript",

Просмотреть файл

@ -11,7 +11,7 @@ import type { Symbols } from "../reducers/ast";
export function findFunctionText(
line: number,
source: SourceWithContent,
{ source, content }: SourceWithContent,
symbols: ?Symbols
): ?string {
const func = findClosestFunction(symbols, {
@ -23,9 +23,9 @@ export function findFunctionText(
if (
source.isWasm ||
!func ||
!source.content ||
!isFulfilled(source.content) ||
source.content.value.type !== "text"
!content ||
!isFulfilled(content) ||
content.value.type !== "text"
) {
return null;
}
@ -33,7 +33,7 @@ export function findFunctionText(
const {
location: { start, end },
} = func;
const lines = source.content.value.value.split("\n");
const lines = content.value.value.split("\n");
const firstLine = lines[start.line - 1].slice(start.column);
const lastLine = lines[end.line - 1].slice(0, end.column);
const middle = lines.slice(start.line, end.line - 1);

Просмотреть файл

@ -13,20 +13,16 @@ const INDENT_COUNT_THRESHOLD = 5;
const CHARACTER_LIMIT = 250;
const _minifiedCache = new Map();
export function isMinified(source: SourceWithContent) {
export function isMinified({ source, content }: SourceWithContent) {
if (_minifiedCache.has(source.id)) {
return _minifiedCache.get(source.id);
}
if (
!source.content ||
!isFulfilled(source.content) ||
source.content.value.type !== "text"
) {
if (!content || !isFulfilled(content) || content.value.type !== "text") {
return false;
}
let text = source.content.value.value;
let text = content.value.value;
let lineEndIndex = 0;
let lineStartIndex = 0;

Просмотреть файл

@ -5,22 +5,25 @@
// @flow
import type { ThunkArgs } from "../actions/types";
import { asSettled, type AsyncValue } from "./async-value";
export type MemoizedAction<
Args,
Result
> = Args => ThunkArgs => Promise<Result | null>;
> = Args => ThunkArgs => Promise<?Result>;
type MemoizableActionParams<Args, Result> = {
getValue: (args: Args, thunkArgs: ThunkArgs) => AsyncValue<Result> | null,
exitEarly?: (args: Args, thunkArgs: ThunkArgs) => boolean,
hasValue: (args: Args, thunkArgs: ThunkArgs) => boolean,
getValue: (args: Args, thunkArgs: ThunkArgs) => Result,
createKey: (args: Args, thunkArgs: ThunkArgs) => string,
action: (args: Args, thunkArgs: ThunkArgs) => Promise<mixed>,
action: (args: Args, thunkArgs: ThunkArgs) => Promise<Result>,
};
/*
* memoizableActon is a utility for actions that should only be performed
* once per key. It is useful for loading sources, parsing symbols ...
*
* @exitEarly - if true, do not attempt to perform the action
* @hasValue - checks to see if the result is in the redux store
* @getValue - gets the result from the redux store
* @createKey - creates a key for the requests map
* @action - kicks off the async work for the action
@ -41,47 +44,41 @@ type MemoizableActionParams<Args, Result> = {
*/
export function memoizeableAction<Args, Result>(
name: string,
{ getValue, createKey, action }: MemoizableActionParams<Args, Result>
{
hasValue,
getValue,
createKey,
action,
exitEarly,
}: MemoizableActionParams<Args, Result>
): MemoizedAction<Args, Result> {
const requests = new Map();
return args => async thunkArgs => {
let result = asSettled(getValue(args, thunkArgs));
if (!result) {
const key = createKey(args, thunkArgs);
if (!requests.has(key)) {
requests.set(
key,
(async () => {
try {
await action(args, thunkArgs);
} catch (e) {
console.warn(`Action ${name} had an exception:`, e);
} finally {
requests.delete(key);
}
})()
);
}
await requests.get(key);
result = asSettled(getValue(args, thunkArgs));
if (!result) {
// Returning null here is not ideal. This means that the action
// resolved but 'getValue' didn't return a loaded value, for instance
// if the data the action was meant to store was deleted. In a perfect
// world we'd throw a ContextError here or handle cancellation somehow.
// Throwing will also allow us to change the return type on the action
// to always return a promise for the getValue AsyncValue type, but
// for now we have to add an additional '| null' for this case.
return null;
}
return args => async (thunkArgs: ThunkArgs) => {
if (exitEarly && exitEarly(args, thunkArgs)) {
return;
}
if (result.state === "rejected") {
throw result.value;
if (hasValue(args, thunkArgs)) {
return getValue(args, thunkArgs);
}
return result.value;
const key = createKey(args, thunkArgs);
if (!requests.has(key)) {
requests.set(
key,
(async () => {
try {
await action(args, thunkArgs);
} catch (e) {
console.warn(`Action ${name} had an exception:`, e);
} finally {
requests.delete(key);
}
})()
);
}
await requests.get(key);
return getValue(args, thunkArgs);
};
}

Просмотреть файл

@ -27,12 +27,8 @@ import type {
Why,
} from "../types";
import * as asyncValue from "./async-value";
import type { SourceBase } from "../reducers/sources";
function makeMockSource(
url: string = "url",
id: SourceId = "source"
): SourceBase {
function makeMockSource(url: string = "url", id: SourceId = "source"): Source {
return {
id,
url,
@ -56,7 +52,7 @@ function makeMockSourceWithContent(
const source = makeMockSource(url, id);
return {
...source,
source,
content: text
? asyncValue.fulfilled({
type: "text",
@ -72,11 +68,11 @@ function makeMockSourceAndContent(
id?: SourceId,
contentType?: string = "text/javascript",
text: string = ""
): { ...SourceBase, content: TextSourceContent } {
): { source: Source, content: TextSourceContent } {
const source = makeMockSource(url, id);
return {
...source,
source,
content: {
type: "text",
value: text,
@ -85,7 +81,7 @@ function makeMockSourceAndContent(
};
}
function makeMockWasmSource(): SourceBase {
function makeMockWasmSource(): Source {
return {
id: "wasm-source-id",
url: "url",
@ -106,7 +102,7 @@ function makeMockWasmSourceWithContent(text: {|
const source = makeMockWasmSource();
return {
...source,
source,
content: asyncValue.fulfilled({
type: "wasm",
value: text,

Просмотреть файл

@ -10,7 +10,7 @@ import { getSymbols } from "../../workers/parser/getSymbols";
import { populateSource } from "../../workers/parser/tests/helpers";
describe("find the best expression for the token", () => {
const source = populateSource("computed-props");
const { source } = populateSource("computed-props");
const symbols = getSymbols(source.id);
it("should find the identifier", () => {

Просмотреть файл

@ -13,14 +13,14 @@ describe("function", () => {
describe("findFunctionText", () => {
it("finds function", () => {
const source = populateOriginalSource("func");
const symbols = getSymbols(source.id);
const symbols = getSymbols(source.source.id);
const text = findFunctionText(14, source, symbols);
expect(text).toMatchSnapshot();
});
it("finds function signature", () => {
const source = populateOriginalSource("func");
const symbols = getSymbols(source.id);
const symbols = getSymbols(source.source.id);
const text = findFunctionText(13, source, symbols);
expect(text).toMatchSnapshot();
@ -28,7 +28,7 @@ describe("function", () => {
it("misses function closing brace", () => {
const source = populateOriginalSource("func");
const symbols = getSymbols(source.id);
const symbols = getSymbols(source.source.id);
const text = findFunctionText(15, source, symbols);
@ -38,7 +38,7 @@ describe("function", () => {
it("finds property function", () => {
const source = populateOriginalSource("func");
const symbols = getSymbols(source.id);
const symbols = getSymbols(source.source.id);
const text = findFunctionText(29, source, symbols);
expect(text).toMatchSnapshot();
@ -46,7 +46,7 @@ describe("function", () => {
it("finds class function", () => {
const source = populateOriginalSource("func");
const symbols = getSymbols(source.id);
const symbols = getSymbols(source.source.id);
const text = findFunctionText(33, source, symbols);
expect(text).toMatchSnapshot();
@ -54,7 +54,7 @@ describe("function", () => {
it("cant find function", () => {
const source = populateOriginalSource("func");
const symbols = getSymbols(source.id);
const symbols = getSymbols(source.source.id);
const text = findFunctionText(20, source, symbols);
expect(text).toEqual(null);

Просмотреть файл

@ -237,43 +237,47 @@ describe("sources", () => {
describe("isJavaScript", () => {
it("is not JavaScript", () => {
{
const source = makeMockSourceAndContent("foo.html", undefined, "");
expect(isJavaScript(source, source.content)).toBe(false);
const { source, content } = makeMockSourceAndContent(
"foo.html",
undefined,
""
);
expect(isJavaScript(source, content)).toBe(false);
}
{
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/html"
);
expect(isJavaScript(source, source.content)).toBe(false);
expect(isJavaScript(source, content)).toBe(false);
}
});
it("is JavaScript", () => {
{
const source = makeMockSourceAndContent("foo.js");
expect(isJavaScript(source, source.content)).toBe(true);
const { source, content } = makeMockSourceAndContent("foo.js");
expect(isJavaScript(source, content)).toBe(true);
}
{
const source = makeMockSourceAndContent("bar.jsm");
expect(isJavaScript(source, source.content)).toBe(true);
const { source, content } = makeMockSourceAndContent("bar.jsm");
expect(isJavaScript(source, content)).toBe(true);
}
{
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/javascript"
);
expect(isJavaScript(source, source.content)).toBe(true);
expect(isJavaScript(source, content)).toBe(true);
}
{
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"application/javascript"
);
expect(isJavaScript(source, source.content)).toBe(true);
expect(isJavaScript(source, content)).toBe(true);
}
});
});
@ -296,124 +300,121 @@ describe("sources", () => {
describe("getMode", () => {
it("//@flow", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/javascript",
"// @flow"
);
expect(getMode(source, source.content)).toEqual({
expect(getMode(source, content)).toEqual({
name: "javascript",
typescript: true,
});
});
it("/* @flow */", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/javascript",
" /* @flow */"
);
expect(getMode(source, source.content)).toEqual({
expect(getMode(source, content)).toEqual({
name: "javascript",
typescript: true,
});
});
it("mixed html", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"",
" <html"
);
expect(getMode(source, source.content)).toEqual({ name: "htmlmixed" });
expect(getMode(source, content)).toEqual({ name: "htmlmixed" });
});
it("elm", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/x-elm",
'main = text "Hello, World!"'
);
expect(getMode(source, source.content)).toEqual({ name: "elm" });
expect(getMode(source, content)).toEqual({ name: "elm" });
});
it("returns jsx if contentType jsx is given", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/jsx",
"<h1></h1>"
);
expect(getMode(source, source.content)).toEqual({ name: "jsx" });
expect(getMode(source, content)).toEqual({ name: "jsx" });
});
it("returns jsx if sourceMetaData says it's a react component", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"",
"<h1></h1>"
);
expect(
getMode(source, source.content, {
...defaultSymbolDeclarations,
hasJsx: true,
})
getMode(source, content, { ...defaultSymbolDeclarations, hasJsx: true })
).toEqual({ name: "jsx" });
});
it("returns jsx if the fileExtension is .jsx", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"myComponent.jsx",
undefined,
"",
"<h1></h1>"
);
expect(getMode(source, source.content)).toEqual({ name: "jsx" });
expect(getMode(source, content)).toEqual({ name: "jsx" });
});
it("returns text/x-haxe if the file extension is .hx", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"myComponent.hx",
undefined,
"",
"function foo(){}"
);
expect(getMode(source, source.content)).toEqual({ name: "text/x-haxe" });
expect(getMode(source, content)).toEqual({ name: "text/x-haxe" });
});
it("typescript", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/typescript",
"function foo(){}"
);
expect(getMode(source, source.content)).toEqual({
expect(getMode(source, content)).toEqual({
name: "javascript",
typescript: true,
});
});
it("typescript-jsx", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/typescript-jsx",
"<h1></h1>"
);
expect(getMode(source, source.content).base).toEqual({
expect(getMode(source, content).base).toEqual({
name: "javascript",
typescript: true,
});
});
it("cross-platform clojure(script) with reader conditionals", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"my-clojurescript-source-with-reader-conditionals.cljc",
undefined,
"text/x-clojure",
@ -421,54 +422,54 @@ describe("sources", () => {
" #?(:clj (java.lang.Integer/parseInt s) " +
" :cljs (js/parseInt s)))"
);
expect(getMode(source, source.content)).toEqual({ name: "clojure" });
expect(getMode(source, content)).toEqual({ name: "clojure" });
});
it("clojurescript", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"my-clojurescript-source.cljs",
undefined,
"text/x-clojurescript",
"(+ 1 2 3)"
);
expect(getMode(source, source.content)).toEqual({ name: "clojure" });
expect(getMode(source, content)).toEqual({ name: "clojure" });
});
it("coffeescript", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
undefined,
undefined,
"text/coffeescript",
"x = (a) -> 3"
);
expect(getMode(source, source.content)).toEqual({ name: "coffeescript" });
expect(getMode(source, content)).toEqual({ name: "coffeescript" });
});
it("wasm", () => {
const source = makeMockWasmSourceWithContent({
const { source, content } = makeMockWasmSourceWithContent({
binary: "\x00asm\x01\x00\x00\x00",
});
expect(getMode(source, source.content.value)).toEqual({ name: "text" });
expect(getMode(source, content.value)).toEqual({ name: "text" });
});
it("marko", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"http://localhost.com:7999/increment/sometestfile.marko",
undefined,
"does not matter",
"function foo(){}"
);
expect(getMode(source, source.content)).toEqual({ name: "javascript" });
expect(getMode(source, content)).toEqual({ name: "javascript" });
});
it("es6", () => {
const source = makeMockSourceAndContent(
const { source, content } = makeMockSourceAndContent(
"http://localhost.com:7999/increment/sometestfile.es6",
undefined,
"does not matter",
"function foo(){}"
);
expect(getMode(source, source.content)).toEqual({ name: "javascript" });
expect(getMode(source, content)).toEqual({ name: "javascript" });
});
});

Просмотреть файл

@ -37,8 +37,8 @@ describe("wasm", () => {
expect(isWasm(sourceId)).toEqual(false);
});
it("should give us the true when wasm text was registered", () => {
const source = makeMockWasmSourceWithContent(SIMPLE_WASM);
renderWasmText(source.id, source.content.value);
const { source, content } = makeMockWasmSourceWithContent(SIMPLE_WASM);
renderWasmText(source.id, content.value);
expect(isWasm(source.id)).toEqual(true);
// clear shall remove
clearWasmStates();
@ -48,8 +48,8 @@ describe("wasm", () => {
describe("renderWasmText", () => {
it("render simple wasm", () => {
const source = makeMockWasmSourceWithContent(SIMPLE_WASM);
const lines = renderWasmText(source.id, source.content.value);
const { source, content } = makeMockWasmSourceWithContent(SIMPLE_WASM);
const lines = renderWasmText(source.id, content.value);
expect(lines.join("\n")).toEqual(SIMPLE_WASM_TEXT);
clearWasmStates();
});
@ -60,8 +60,8 @@ describe("wasm", () => {
expect(SIMPLE_WASM.binary[SIMPLE_WASM_NOP_OFFSET]).toEqual("\x01");
it("get simple wasm nop offset", () => {
const source = makeMockWasmSourceWithContent(SIMPLE_WASM);
renderWasmText(source.id, source.content.value);
const { source, content } = makeMockWasmSourceWithContent(SIMPLE_WASM);
renderWasmText(source.id, content.value);
const offset = lineToWasmOffset(source.id, SIMPLE_WASM_NOP_TEXT_LINE);
expect(offset).toEqual(SIMPLE_WASM_NOP_OFFSET);
clearWasmStates();
@ -70,8 +70,8 @@ describe("wasm", () => {
describe("wasmOffsetToLine", () => {
it("get simple wasm nop line", () => {
const source = makeMockWasmSourceWithContent(SIMPLE_WASM);
renderWasmText(source.id, source.content.value);
const { source, content } = makeMockWasmSourceWithContent(SIMPLE_WASM);
renderWasmText(source.id, content.value);
const line = wasmOffsetToLine(source.id, SIMPLE_WASM_NOP_OFFSET);
expect(line).toEqual(SIMPLE_WASM_NOP_TEXT_LINE);
clearWasmStates();

Просмотреть файл

@ -20,7 +20,7 @@ function formatLines(actual) {
describe("Parser.findOutOfScopeLocations", () => {
it("should exclude non-enclosing function blocks", () => {
const source = populateSource("outOfScope");
const { source } = populateSource("outOfScope");
const actual = findOutOfScopeLocations(source.id, {
line: 5,
column: 5,
@ -30,7 +30,7 @@ describe("Parser.findOutOfScopeLocations", () => {
});
it("should roll up function blocks", () => {
const source = populateSource("outOfScope");
const { source } = populateSource("outOfScope");
const actual = findOutOfScopeLocations(source.id, {
line: 24,
column: 0,
@ -40,7 +40,7 @@ describe("Parser.findOutOfScopeLocations", () => {
});
it("should exclude function for locations on declaration", () => {
const source = populateSource("outOfScope");
const { source } = populateSource("outOfScope");
const actual = findOutOfScopeLocations(source.id, {
line: 3,
column: 12,
@ -50,7 +50,7 @@ describe("Parser.findOutOfScopeLocations", () => {
});
it("should treat comments as out of scope", () => {
const source = populateSource("outOfScopeComment");
const { source } = populateSource("outOfScopeComment");
const actual = findOutOfScopeLocations(source.id, {
line: 3,
column: 2,
@ -62,7 +62,7 @@ describe("Parser.findOutOfScopeLocations", () => {
});
it("should not exclude in-scope inner locations", () => {
const source = populateSource("outOfScope");
const { source } = populateSource("outOfScope");
const actual = findOutOfScopeLocations(source.id, {
line: 61,
column: 0,

Просмотреть файл

@ -11,7 +11,7 @@ import cases from "jest-in-case";
cases(
"Parser.getFramework",
({ name, file, value }) => {
const source = populateOriginalSource("frameworks/plainJavascript");
const { source } = populateOriginalSource("frameworks/plainJavascript");
const symbols = getSymbols(source.id);
expect(symbols.framework).toBeUndefined();
},

Просмотреть файл

@ -12,7 +12,7 @@ import cases from "jest-in-case";
cases(
"Parser.getScopes",
({ name, file, type, locations }) => {
const source = populateOriginalSource(file, type);
const { source } = populateOriginalSource(file, type);
locations.forEach(([line, column]) => {
const scopes = getScopes({

Просмотреть файл

@ -12,7 +12,7 @@ import cases from "jest-in-case";
cases(
"Parser.getSymbols",
({ name, file, original, type }) => {
const source = original
const { source } = original
? populateOriginalSource(file, type)
: populateSource(file, type);

Просмотреть файл

@ -10,7 +10,6 @@ import path from "path";
import type {
Source,
TextSourceContent,
SourceBase,
SourceWithContent,
} from "../../../../types";
import { makeMockSourceAndContent } from "../../../../utils/test-mockup";
@ -48,20 +47,20 @@ function getSourceContent(
}
export function getSource(name: string, type?: string): Source {
return getSourceWithContent(name, type);
return getSourceWithContent(name, type).source;
}
export function getSourceWithContent(
name: string,
type?: string
): { ...SourceBase, content: TextSourceContent } {
): { source: Source, content: TextSourceContent } {
const { value: text, contentType } = getSourceContent(name, type);
return makeMockSourceAndContent(undefined, name, contentType, text);
}
export function populateSource(name: string, type?: string): SourceWithContent {
const { content, ...source } = getSourceWithContent(name, type);
const { source, content } = getSourceWithContent(name, type);
setSource({
id: source.id,
text: content.value,
@ -69,19 +68,19 @@ export function populateSource(name: string, type?: string): SourceWithContent {
isWasm: false,
});
return {
...source,
source,
content: asyncValue.fulfilled(content),
};
}
export function getOriginalSource(name: string, type?: string): Source {
return getOriginalSourceWithContent(name, type);
return getOriginalSourceWithContent(name, type).source;
}
export function getOriginalSourceWithContent(
name: string,
type?: string
): { ...SourceBase, content: TextSourceContent } {
): { source: Source, content: TextSourceContent } {
const { value: text, contentType } = getSourceContent(name, type);
return makeMockSourceAndContent(
@ -96,7 +95,7 @@ export function populateOriginalSource(
name: string,
type?: string
): SourceWithContent {
const { content, ...source } = getOriginalSourceWithContent(name, type);
const { source, content } = getOriginalSourceWithContent(name, type);
setSource({
id: source.id,
text: content.value,
@ -104,7 +103,7 @@ export function populateOriginalSource(
isWasm: false,
});
return {
...source,
source,
content: asyncValue.fulfilled(content),
};
}

Просмотреть файл

@ -10,7 +10,7 @@ import { populateSource } from "./helpers";
describe("getNextStep", () => {
describe("await", () => {
it("first await call", () => {
const source = populateSource("async");
const { source } = populateSource("async");
const pausePosition = { line: 8, column: 2, sourceId: source.id };
expect(getNextStep(source.id, pausePosition)).toEqual({
...pausePosition,
@ -19,7 +19,7 @@ describe("getNextStep", () => {
});
it("first await call expression", () => {
const source = populateSource("async");
const { source } = populateSource("async");
const pausePosition = { line: 8, column: 9, sourceId: source.id };
expect(getNextStep(source.id, pausePosition)).toEqual({
...pausePosition,
@ -29,13 +29,13 @@ describe("getNextStep", () => {
});
it("second await call", () => {
const source = populateSource("async");
const { source } = populateSource("async");
const pausePosition = { line: 9, column: 2, sourceId: source.id };
expect(getNextStep(source.id, pausePosition)).toEqual(null);
});
it("second call expression", () => {
const source = populateSource("async");
const { source } = populateSource("async");
const pausePosition = { line: 9, column: 9, sourceId: source.id };
expect(getNextStep(source.id, pausePosition)).toEqual(null);
});
@ -43,7 +43,7 @@ describe("getNextStep", () => {
describe("yield", () => {
it("first yield call", () => {
const source = populateSource("generators");
const { source } = populateSource("generators");
const pausePosition = { line: 2, column: 2, sourceId: source.id };
expect(getNextStep(source.id, pausePosition)).toEqual({
...pausePosition,
@ -52,7 +52,7 @@ describe("getNextStep", () => {
});
it("second yield call", () => {
const source = populateSource("generators");
const { source } = populateSource("generators");
const pausePosition = { line: 3, column: 2, sourceId: source.id };
expect(getNextStep(source.id, pausePosition)).toEqual(null);
});

Просмотреть файл

@ -23,11 +23,16 @@ const astKeys = [
cases(
"ast.getAst",
({ name }) => {
const source = makeMockSourceAndContent(undefined, "foo", name, "2");
const { source, content } = makeMockSourceAndContent(
undefined,
"foo",
name,
"2"
);
setSource({
id: source.id,
text: source.content.value || "",
contentType: source.content.contentType,
text: content.value || "",
contentType: content.contentType,
isWasm: false,
});
const ast = getAst("foo");

Просмотреть файл

@ -241,8 +241,8 @@ function waitForSelectedSource(dbg, url) {
return waitForState(
dbg,
state => {
const source = getSelectedSourceWithContent() || {};
if (!source.content) {
const { source, content } = getSelectedSourceWithContent() || {};
if (!content) {
return false;
}
@ -318,8 +318,9 @@ function assertPausedLocation(dbg) {
function assertDebugLine(dbg, line) {
// Check the debug line
const lineInfo = getCM(dbg).lineInfo(line - 1);
const source = dbg.selectors.getSelectedSourceWithContent() || {};
if (source && !source.content) {
const { source, content } =
dbg.selectors.getSelectedSourceWithContent() || {};
if (source && !content) {
const url = source.url;
ok(
false,
@ -514,9 +515,14 @@ function isSelectedFrameSelected(dbg, state) {
// Make sure the source text is completely loaded for the
// source we are paused in.
const sourceId = frame.location.sourceId;
const source = dbg.selectors.getSelectedSourceWithContent() || {};
const { source, content } =
dbg.selectors.getSelectedSourceWithContent() || {};
if (!source || !source.content) {
if (!source) {
return false;
}
if (!content) {
return false;
}