Bug 1534332 - get empty lines r=loganfsmyth
authorJason Laster <jlaster@mozilla.com>
Tue, 12 Mar 2019 21:07:43 +0000
changeset 521602 6226618708d29c9bd5c4addb8dfe006ac40cab62
parent 521601 2f068b111006dfcd21caa9d10579a5569c8ea48c
child 521603 356a982f5ba41e6f091023a4fd5aaf5c6532f929
push id10867
push userdvarga@mozilla.com
push dateThu, 14 Mar 2019 15:20:45 +0000
treeherdermozilla-beta@abad13547875 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersloganfsmyth
bugs1534332
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1534332 - get empty lines r=loganfsmyth Differential Revision: https://phabricator.services.mozilla.com/D22985
devtools/client/debugger/new/src/actions/breakpoints/breakpointPositions.js
devtools/client/debugger/new/src/actions/breakpoints/index.js
devtools/client/debugger/new/src/actions/types/BreakpointAction.js
devtools/client/debugger/new/src/reducers/ast.js
devtools/client/debugger/new/src/reducers/breakpoints.js
devtools/client/debugger/new/src/utils/empty-lines.js
devtools/client/debugger/new/src/utils/moz.build
devtools/client/debugger/new/src/utils/tests/empty-lines.spec.js
devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js
--- a/devtools/client/debugger/new/src/actions/breakpoints/breakpointPositions.js
+++ b/devtools/client/debugger/new/src/actions/breakpoints/breakpointPositions.js
@@ -92,17 +92,27 @@ async function _setBreakpointPositions(s
     }
   } else {
     results = await client.getBreakpointPositions(generatedSource);
   }
 
   let positions = convertToList(results, generatedSource);
   positions = await mapLocations(positions, thunkArgs);
   positions = filterByUniqLocation(positions);
-  dispatch({ type: "ADD_BREAKPOINT_POSITIONS", sourceId, positions });
+
+  const source = getSource(getState(), sourceId);
+  // NOTE: it's possible that the source was removed during a navigate
+  if (!source) {
+    return;
+  }
+  dispatch({
+    type: "ADD_BREAKPOINT_POSITIONS",
+    source: source,
+    positions
+  });
 }
 
 export function setBreakpointPositions(sourceId: string) {
   return async (thunkArgs: ThunkArgs) => {
     const { getState } = thunkArgs;
     if (hasBreakpointPositions(getState(), sourceId)) {
       return getBreakpointPositionsForSource(getState(), sourceId);
     }
--- a/devtools/client/debugger/new/src/actions/breakpoints/index.js
+++ b/devtools/client/debugger/new/src/actions/breakpoints/index.js
@@ -12,32 +12,32 @@
 import { PROMISE } from "../utils/middleware/promise";
 import {
   getBreakpoint,
   getBreakpointsList,
   getXHRBreakpoints,
   getSelectedSource,
   getBreakpointAtLocation,
   getConditionalPanelLocation,
-  getBreakpointsForSource
+  getBreakpointsForSource,
+  isEmptyLineInSource
 } from "../../selectors";
 import {
   assertBreakpoint,
   createXHRBreakpoint,
   makeBreakpointLocation
 } from "../../utils/breakpoint";
 import {
   addBreakpoint,
   addHiddenBreakpoint,
   enableBreakpoint
 } from "./addBreakpoint";
 import remapLocations from "./remapLocations";
 import { syncBreakpoint } from "./syncBreakpoint";
 import { closeConditionalPanel } from "../ui";
-import { isEmptyLineInSource } from "../../reducers/ast";
 
 // this will need to be changed so that addCLientBreakpoint is removed
 
 import type { ThunkArgs, Action } from "../types";
 import type {
   Breakpoint,
   BreakpointOptions,
   Source,
--- a/devtools/client/debugger/new/src/actions/types/BreakpointAction.js
+++ b/devtools/client/debugger/new/src/actions/types/BreakpointAction.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import type {
   Breakpoint,
   SourceLocation,
   XHRBreakpoint,
+  Source,
   BreakpointPositions
 } from "../../types";
 
 import type { PromiseAction } from "../utils/middleware/promise";
 
 export type BreakpointAction =
   | PromiseAction<
       {|
@@ -90,10 +91,10 @@ export type BreakpointAction =
     |}
   | {|
       +type: "REMAP_BREAKPOINTS",
       +breakpoints: Breakpoint[]
     |}
   | {|
       type: "ADD_BREAKPOINT_POSITIONS",
       positions: BreakpointPositions,
-      sourceId: string
+      source: Source
     |};
--- a/devtools/client/debugger/new/src/reducers/ast.js
+++ b/devtools/client/debugger/new/src/reducers/ast.js
@@ -161,25 +161,16 @@ export function isSymbolsLoading(state: 
 export function getOutOfScopeLocations(state: OuterState) {
   return state.ast.outOfScopeLocations;
 }
 
 export function getPreview(state: OuterState) {
   return state.ast.preview;
 }
 
-export function isEmptyLineInSource(
-  state: OuterState,
-  line: number,
-  selectedSourceId: string
-) {
-  const emptyLines = getEmptyLines(state, selectedSourceId);
-  return emptyLines && emptyLines.includes(line);
-}
-
 const emptySourceMetaData = {};
 export function getSourceMetaData(state: OuterState, sourceId: string) {
   return state.ast.sourceMetaData[sourceId] || emptySourceMetaData;
 }
 
 export function hasSourceMetaData(state: OuterState, sourceId: string) {
   return state.ast.sourceMetaData[sourceId];
 }
@@ -188,17 +179,9 @@ export function getInScopeLines(state: O
   return state.ast.inScopeLines;
 }
 
 export function isLineInScope(state: OuterState, line: number) {
   const linesInScope = state.ast.inScopeLines;
   return linesInScope && linesInScope.includes(line);
 }
 
-export function getEmptyLines(state: OuterState, sourceId: string) {
-  if (!sourceId) {
-    return null;
-  }
-
-  return state.ast.emptyLines[sourceId];
-}
-
 export default update;
--- a/devtools/client/debugger/new/src/reducers/breakpoints.js
+++ b/devtools/client/debugger/new/src/reducers/breakpoints.js
@@ -8,16 +8,17 @@
  * Breakpoints reducer
  * @module reducers/breakpoints
  */
 
 import { isGeneratedId, isOriginalId } from "devtools-source-map";
 import { isEqual } from "lodash";
 
 import { makeBreakpointId } from "../utils/breakpoint";
+import { findEmptyLines } from "../utils/empty-lines";
 
 import type {
   XHRBreakpoint,
   Breakpoint,
   BreakpointId,
   SourceLocation,
   BreakpointPositions
 } from "../types";
@@ -26,27 +27,29 @@ import type { Action, DonePromiseAction 
 export type BreakpointsMap = { [BreakpointId]: Breakpoint };
 export type XHRBreakpointsList = $ReadOnlyArray<XHRBreakpoint>;
 export type BreakpointPositionsMap = { [string]: BreakpointPositions };
 
 export type BreakpointsState = {
   breakpoints: BreakpointsMap,
   breakpointPositions: BreakpointPositionsMap,
   xhrBreakpoints: XHRBreakpointsList,
-  breakpointsDisabled: boolean
+  breakpointsDisabled: boolean,
+  emptyLines: { [string]: number[] }
 };
 
 export function initialBreakpointsState(
   xhrBreakpoints?: XHRBreakpointsList = []
 ): BreakpointsState {
   return {
     breakpoints: {},
     xhrBreakpoints: xhrBreakpoints,
     breakpointPositions: {},
-    breakpointsDisabled: false
+    breakpointsDisabled: false,
+    emptyLines: {}
   };
 }
 
 function update(
   state: BreakpointsState = initialBreakpointsState(),
   action: Action
 ): BreakpointsState {
   switch (action.type) {
@@ -106,22 +109,28 @@ function update(
       return updateXHRBreakpoint(state, action);
     }
 
     case "DISABLE_XHR_BREAKPOINT": {
       return updateXHRBreakpoint(state, action);
     }
 
     case "ADD_BREAKPOINT_POSITIONS": {
-      const { sourceId, positions } = action;
+      const { source, positions } = action;
+      const emptyLines = findEmptyLines(source, positions);
+
       return {
         ...state,
         breakpointPositions: {
           ...state.breakpointPositions,
-          [sourceId]: positions
+          [source.id]: positions
+        },
+        emptyLines: {
+          ...state.emptyLines,
+          [source.id]: emptyLines
         }
       };
     }
   }
 
   return state;
 }
 
@@ -378,9 +387,26 @@ export function getBreakpointPositionsFo
     return null;
   }
   return positions.filter(({ location, generatedLocation }) => {
     const loc = isOriginalId(sourceId) ? location : generatedLocation;
     return loc.line == line;
   });
 }
 
+export function isEmptyLineInSource(
+  state: OuterState,
+  line: number,
+  selectedSourceId: string
+) {
+  const emptyLines = getEmptyLines(state, selectedSourceId);
+  return emptyLines && emptyLines.includes(line);
+}
+
+export function getEmptyLines(state: OuterState, sourceId: string) {
+  if (!sourceId) {
+    return null;
+  }
+
+  return state.breakpoints.emptyLines[sourceId];
+}
+
 export default update;
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/utils/empty-lines.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import { xor, range } from "lodash";
+import { getSelectedLocation } from "./source-maps";
+import type { BreakpointPositions, Source } from "../types";
+
+export function findEmptyLines(
+  source: Source,
+  breakpointPositions: BreakpointPositions
+): number[] {
+  if (!breakpointPositions || source.isWasm) {
+    return [];
+  }
+
+  const sourceText = source.text || "";
+  const lineCount = sourceText.split("\n").length;
+  const sourceLines = range(1, lineCount + 1);
+
+  const breakpointLines = breakpointPositions
+    .map(point => getSelectedLocation(point, source).line)
+    // NOTE: at the moment it is possible the location is an unmapped generated
+    // line which could be greater than the line count.
+    .filter(line => line <= lineCount);
+
+  return xor(sourceLines, breakpointLines);
+}
--- a/devtools/client/debugger/new/src/utils/moz.build
+++ b/devtools/client/debugger/new/src/utils/moz.build
@@ -16,16 +16,17 @@ CompiledModules(
     'asyncStoreHelper.js',
     'bootstrap.js',
     'build-query.js',
     'clipboard.js',
     'connect.js',
     'dbg.js',
     'defer.js',
     'DevToolsUtils.js',
+    'empty-lines.js',
     'expressions.js',
     'fromJS.js',
     'function.js',
     'indentation.js',
     'isMinified.js',
     'location.js',
     'log.js',
     'makeRecord.js',
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/utils/tests/empty-lines.spec.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// @flow
+
+import { findEmptyLines } from "../empty-lines";
+import { makeSource } from "../test-head";
+
+function ml(gLine) {
+  const generatedLocation = { line: gLine, column: 0, sourceId: "foo" };
+  return { generatedLocation, location: generatedLocation };
+}
+
+describe("emptyLines", () => {
+  it("no positions", () => {
+    const source = makeSource("foo", { text: "\n" });
+    const lines = findEmptyLines(source, []);
+    expect(lines).toEqual([1, 2]);
+  });
+
+  it("one position", () => {
+    const source = makeSource("foo", { text: "\n" });
+    const lines = findEmptyLines(source, [ml(1)]);
+    expect(lines).toEqual([2]);
+  });
+
+  it("outside positions are not included", () => {
+    const source = makeSource("foo", { text: "\n" });
+    const lines = findEmptyLines(source, [ml(10)]);
+    expect(lines).toEqual([1, 2]);
+  });
+});
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js
@@ -30,16 +30,30 @@ function disableBreakpoints(dbg, count) 
 }
 
 function enableBreakpoints(dbg, count) {
   const enabled = waitForDispatch(dbg, "ENABLE_BREAKPOINT", count);
   toggleBreakpoints(dbg);
   return enabled;
 }
 
+function every(array, predicate) {
+  return !array.some(item => !predicate(item));
+}
+
+function subset(subArray, superArray) {
+  return every(subArray, subItem => superArray.includes(subItem));
+}
+
+function assertEmptyLines(dbg, lines) {
+  const sourceId = dbg.selectors.getSelectedSourceId(dbg.store.getState());
+  const emptyLines = dbg.selectors.getEmptyLines(dbg.store.getState(), sourceId);
+  ok(subset(lines, emptyLines), 'empty lines should match');
+}
+
 // Test enabling and disabling a breakpoint using the check boxes
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html", "simple2");
 
   // Create two breakpoints
   await selectSource(dbg, "simple2");
   await addBreakpoint(dbg, "simple2", 3);
   await addBreakpoint(dbg, "simple2", 5);
@@ -60,16 +74,18 @@ add_task(async function() {
 
 // Test enabling and disabling a breakpoint using the context menu
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html");
   await selectSource(dbg, "simple2");
   await addBreakpoint(dbg, "simple2", 3);
   await addBreakpoint(dbg, "simple2", 5);
 
+  assertEmptyLines(dbg, [1,2]);
+
   rightClickElement(dbg, "breakpointItem", 3);
   const disableBreakpointDispatch = waitForDispatch(dbg, "DISABLE_BREAKPOINT");
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableSelf);
   await disableBreakpointDispatch;
 
   let bp1 = findBreakpoint(dbg, "simple2", 3);
   let bp2 = findBreakpoint(dbg, "simple2", 5);
   is(bp1.disabled, true, "first breakpoint is disabled");