Merge branch 'master' into use_repository_field
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 20cec23..66c6bff 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 13.1.1-dev
+- Add column information to breakpoints to allow precise breakpoint placement.
+
 ## 13.1.0
 - Update _fe_analyzer_shared to version ^38.0.0.
 
diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart
index f1b4a6f..ddc9ed0 100644
--- a/dwds/lib/src/debugging/debugger.dart
+++ b/dwds/lib/src/debugging/debugger.dart
@@ -228,8 +228,9 @@
     int line, {
     int column,
   }) async {
+    column ??= 0;
     checkIsolate('addBreakpoint', isolateId);
-    final breakpoint = await _breakpoints.add(scriptId, line);
+    final breakpoint = await _breakpoints.add(scriptId, line, column);
     _notifyBreakpoint(breakpoint);
     return breakpoint;
   }
@@ -249,7 +250,9 @@
     for (var breakpoint in previousBreakpoints) {
       var scriptRef = await _updatedScriptRefFor(breakpoint);
       var updatedLocation = await _locations.locationForDart(
-          DartUri(scriptRef.uri, _root), _lineNumberFor(breakpoint));
+          DartUri(scriptRef.uri, _root),
+          _lineNumberFor(breakpoint),
+          _columnNumberFor(breakpoint));
       var updatedBreakpoint = _breakpoints._dartBreakpoint(
           scriptRef, updatedLocation, breakpoint.id);
       _breakpoints._note(
@@ -263,7 +266,8 @@
       await addBreakpoint(
           inspector.isolate.id,
           (await _updatedScriptRefFor(breakpoint)).id,
-          _lineNumberFor(breakpoint));
+          _lineNumberFor(breakpoint),
+          column: _columnNumberFor(breakpoint));
     }
   }
 
@@ -324,10 +328,11 @@
     var frame = e.params['callFrames'][0];
     var location = frame['location'];
     var scriptId = location['scriptId'] as String;
-    var lineNumber = location['lineNumber'] as int;
+    var line = location['lineNumber'] as int;
+    var column = location['columnNumber'] as int;
 
     var url = _urlForScriptId(scriptId);
-    return _locations.locationForJs(url, lineNumber + 1);
+    return _locations.locationForJs(url, line, column);
   }
 
   /// The variables visible in a frame in Dart protocol [BoundVariable] form.
@@ -459,9 +464,8 @@
     bool populateVariables = true,
   }) async {
     var location = frame.location;
-    // Chrome is 0 based. Account for this.
-    var line = location.lineNumber + 1;
-    var column = location.columnNumber + 1;
+    var line = location.lineNumber;
+    var column = location.columnNumber;
 
     var url = _urlForScriptId(location.scriptId);
     if (url == null) {
@@ -723,14 +727,20 @@
 
 /// Returns the Dart line number for the provided breakpoint.
 int _lineNumberFor(Breakpoint breakpoint) =>
-    int.parse(breakpoint.id.split('#').last);
+    int.parse(breakpoint.id.split('#').last.split(':').first);
+
+/// Returns the Dart column number for the provided breakpoint.
+int _columnNumberFor(Breakpoint breakpoint) =>
+    int.parse(breakpoint.id.split('#').last.split(':').last);
 
 /// Returns the breakpoint ID for the provided Dart script ID and Dart line
 /// number.
-String breakpointIdFor(String scriptId, int line) => 'bp/$scriptId#$line';
+String breakpointIdFor(String scriptId, int line, int column) =>
+    'bp/$scriptId#$line:$column';
 
 /// Keeps track of the Dart and JS breakpoint Ids that correspond.
 class _Breakpoints extends Domain {
+  final logger = Logger('Breakpoints');
   final _dartIdByJsId = <String, String>{};
   final _jsIdByDartId = <String, String>{};
 
@@ -752,13 +762,16 @@
   }) : super(provider);
 
   Future<Breakpoint> _createBreakpoint(
-      String id, String scriptId, int line) async {
+      String id, String scriptId, int line, int column) async {
     var dartScript = inspector.scriptWithId(scriptId);
     var dartUri = DartUri(dartScript.uri, root);
-    var location = await locations.locationForDart(dartUri, line);
-
+    var location = await locations.locationForDart(dartUri, line, column);
     // TODO: Handle cases where a breakpoint can't be set exactly at that line.
     if (location == null) {
+      logger
+          .fine('Failed to set breakpoint at ${dartScript.uri}:$line:$column: '
+              'Dart location not found for scriptId: $scriptId, '
+              'server path: ${dartUri.serverPath}, root:$root');
       throw RPCError(
           'addBreakpoint',
           102,
@@ -779,10 +792,10 @@
 
   /// Adds a breakpoint at [scriptId] and [line] or returns an existing one if
   /// present.
-  Future<Breakpoint> add(String scriptId, int line) async {
-    final id = breakpointIdFor(scriptId, line);
+  Future<Breakpoint> add(String scriptId, int line, int column) async {
+    final id = breakpointIdFor(scriptId, line, column);
     return _bpByDartId.putIfAbsent(
-        id, () => _createBreakpoint(id, scriptId, line));
+        id, () => _createBreakpoint(id, scriptId, line, column));
   }
 
   /// Create a Dart breakpoint at [location] in [dartScript] with [id].
@@ -792,7 +805,12 @@
       id: id,
       breakpointNumber: int.parse(createId()),
       resolved: true,
-      location: SourceLocation(script: dartScript, tokenPos: location.tokenPos),
+      location: SourceLocation(
+        script: dartScript,
+        tokenPos: location.tokenPos,
+        line: location.dartLocation.line,
+        column: location.dartLocation.column,
+      ),
       enabled: true,
     )..id = id;
     return breakpoint;
@@ -800,9 +818,6 @@
 
   /// Calls the Chrome protocol setBreakpoint and returns the remote ID.
   Future<String> _setJsBreakpoint(Location location) async {
-    // Location is 0 based according to:
-    // https://chromedevtools.github.io/devtools-protocol/tot/Debugger#type-Location
-
     // The module can be loaded from a nested path and contain an ETAG suffix.
     var urlRegex = '.*${location.jsLocation.module}.*';
     // Prevent `Aww, snap!` errors when setting multiple breakpoints
@@ -811,7 +826,8 @@
       var response = await remoteDebugger
           .sendCommand('Debugger.setBreakpointByUrl', params: {
         'urlRegex': urlRegex,
-        'lineNumber': location.jsLocation.line - 1,
+        'lineNumber': location.jsLocation.line,
+        'columnNumber': location.jsLocation.column,
       });
       return response.result['breakpointId'] as String;
     });
diff --git a/dwds/lib/src/debugging/location.dart b/dwds/lib/src/debugging/location.dart
index ed66fc1..1dad90b 100644
--- a/dwds/lib/src/debugging/location.dart
+++ b/dwds/lib/src/debugging/location.dart
@@ -40,6 +40,7 @@
     var dartColumn = entry.sourceColumn;
     var jsLine = lineEntry.line;
     var jsColumn = entry.column;
+
     // lineEntry data is 0 based according to:
     // https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
     return Location._(
@@ -71,21 +72,18 @@
   @override
   String toString() => '[${uri.serverPath}:$line:$column]';
 
-  static DartLocation fromZeroBased(DartUri uri, int line, int column) =>
+  factory DartLocation.fromZeroBased(DartUri uri, int line, int column) =>
       DartLocation._(uri, line + 1, column + 1);
-
-  static DartLocation fromOneBased(DartUri uri, int line, int column) =>
-      DartLocation._(uri, line, column);
 }
 
 /// Location information for a JS source.
 class JsLocation {
   final String module;
 
-  /// 1 based row offset within the JS source code.
+  /// 0 based row offset within the JS source code.
   final int line;
 
-  /// 1 based column offset within the JS source code.
+  /// 0 based column offset within the JS source code.
   final int column;
 
   JsLocation._(
@@ -97,10 +95,9 @@
   @override
   String toString() => '[$module:$line:$column]';
 
-  static JsLocation fromZeroBased(String module, int line, int column) =>
-      JsLocation._(module, line + 1, column + 1);
-
-  static JsLocation fromOneBased(String module, int line, int column) =>
+  // JS Location is 0 based according to:
+  // https://chromedevtools.github.io/devtools-protocol/tot/Debugger#type-Location
+  factory JsLocation.fromZeroBased(String module, int line, int column) =>
       JsLocation._(module, line, column);
 }
 
@@ -155,17 +152,21 @@
   /// Find the [Location] for the given Dart source position.
   ///
   /// The [line] number is 1-based.
-  Future<Location> locationForDart(DartUri uri, int line) async =>
+  Future<Location> locationForDart(DartUri uri, int line, int column) async =>
       (await locationsForDart(uri.serverPath)).firstWhere(
-          (location) => location.dartLocation.line == line,
+          (location) =>
+              location.dartLocation.line == line &&
+              location.dartLocation.column >= column,
           orElse: () => null);
 
   /// Find the [Location] for the given JS source position.
   ///
-  /// The [line] number is 1-based.
-  Future<Location> locationForJs(String url, int line) async =>
+  /// The [line] number is 0-based.
+  Future<Location> locationForJs(String url, int line, int column) async =>
       (await locationsForUrl(url)).firstWhere(
-          (location) => location.jsLocation.line == line,
+          (location) =>
+              location.jsLocation.line == line &&
+              location.jsLocation.column >= column,
           orElse: () => null);
 
   /// Returns the tokenPosTable for the provided Dart script path as defined
diff --git a/dwds/lib/src/debugging/skip_list.dart b/dwds/lib/src/debugging/skip_list.dart
index 994c7be..07bc583 100644
--- a/dwds/lib/src/debugging/skip_list.dart
+++ b/dwds/lib/src/debugging/skip_list.dart
@@ -31,8 +31,8 @@
     var startColumn = 0;
     for (var location in locations) {
       // Account for 1 based.
-      var endLine = location.jsLocation.line - 1;
-      var endColumn = location.jsLocation.column - 1;
+      var endLine = location.jsLocation.line;
+      var endColumn = location.jsLocation.column;
       // Stop before the known location.
       endColumn -= 1;
       if (endColumn < 0) {
@@ -42,8 +42,8 @@
       if (endLine > startLine || endColumn > startColumn) {
         ranges.add(
             _rangeFor(scriptId, startLine, startColumn, endLine, endColumn));
-        startLine = location.jsLocation.line - 1;
-        startColumn = location.jsLocation.column;
+        startLine = location.jsLocation.line;
+        startColumn = location.jsLocation.column + 1;
       }
     }
     ranges.add(_rangeFor(scriptId, startLine, startColumn, maxValue, maxValue));
diff --git a/dwds/lib/src/services/expression_evaluator.dart b/dwds/lib/src/services/expression_evaluator.dart
index 211e9f9..8c1494e 100644
--- a/dwds/lib/src/services/expression_evaluator.dart
+++ b/dwds/lib/src/services/expression_evaluator.dart
@@ -159,8 +159,9 @@
     }
 
     var functionName = jsFrame.functionName;
-    var jsLine = jsFrame.location.lineNumber + 1;
+    var jsLine = jsFrame.location.lineNumber;
     var jsScriptId = jsFrame.location.scriptId;
+    var jsColumn = jsFrame.location.columnNumber;
     var jsScope = await _collectLocalJsScope(jsFrame);
 
     // Find corresponding dart location and scope.
@@ -171,7 +172,7 @@
     // cases. Invent location matching strategy for those cases.
     // [issue 890](https://github.com/dart-lang/webdev/issues/890)
     var url = _urlForScriptId(jsScriptId);
-    var locationMap = await _locations.locationForJs(url, jsLine);
+    var locationMap = await _locations.locationForJs(url, jsLine, jsColumn);
     if (locationMap == null) {
       return _createError(
           ErrorKind.internal,
diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart
index 780a52f..06dc051 100644
--- a/dwds/lib/src/version.dart
+++ b/dwds/lib/src/version.dart
@@ -1,2 +1,2 @@
 // Generated code. Do not modify.
-const packageVersion = '13.1.0';
+const packageVersion = '13.1.1-dev';
diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml
index d300be7..824a3ea 100644
--- a/dwds/pubspec.yaml
+++ b/dwds/pubspec.yaml
@@ -1,6 +1,6 @@
 name: dwds
 # Every time this changes you need to run `dart run build_runner build`.
-version: 13.1.0
+version: 13.1.1-dev
 description: >-
   A service that proxies between the Chrome debug protocol and the Dart VM
   service protocol.
diff --git a/dwds/test/build_daemon_breakpoint_test.dart b/dwds/test/build_daemon_breakpoint_test.dart
index 847aded..876383a 100644
--- a/dwds/test/build_daemon_breakpoint_test.dart
+++ b/dwds/test/build_daemon_breakpoint_test.dart
@@ -74,6 +74,28 @@
         await service.removeBreakpoint(isolate.id, bp.id);
       });
 
+      test('set breakpoint inside a JavaScript line succeeds on', () async {
+        var line = await context.findBreakpointLine(
+            'printNestedObjectMultiLine', isolate.id, mainScript);
+        var column = 0;
+        var bp = await service.addBreakpointWithScriptUri(
+            isolate.id, mainScript.uri, line,
+            column: column);
+        await stream.firstWhere(
+            (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+        expect(bp, isNotNull);
+        expect(
+            bp.location,
+            isA<SourceLocation>()
+                .having((loc) => loc.script, 'script', equals(mainScript))
+                .having((loc) => loc.line, 'line', equals(line))
+                .having((loc) => loc.column, 'column', greaterThan(column)));
+
+        // Remove breakpoint so it doesn't impact other tests.
+        await service.removeBreakpoint(isolate.id, bp.id);
+      });
+
       test('set breakpoint again', () async {
         var line = await context.findBreakpointLine(
             'printLocal', isolate.id, mainScript);
diff --git a/dwds/test/frontend_server_breakpoint_test.dart b/dwds/test/frontend_server_breakpoint_test.dart
index 54cfe87..6e2cef2 100644
--- a/dwds/test/frontend_server_breakpoint_test.dart
+++ b/dwds/test/frontend_server_breakpoint_test.dart
@@ -104,6 +104,29 @@
         // Remove breakpoint so it doesn't impact other tests.
         await service.removeBreakpoint(isolate.id, bp.id);
       });
+
+      test('set breakpoint inside a JavaScript line succeeds', () async {
+        var line = await context.findBreakpointLine(
+            'printNestedObjectMultiLine', isolate.id, mainScript);
+        var column = 0;
+        var bp = await service.addBreakpointWithScriptUri(
+            isolate.id, mainScript.uri, line,
+            column: column);
+
+        await stream.firstWhere(
+            (Event event) => event.kind == EventKind.kPauseBreakpoint);
+
+        expect(bp, isNotNull);
+        expect(
+            bp.location,
+            isA<SourceLocation>()
+                .having((loc) => loc.script, 'script', equals(mainScript))
+                .having((loc) => loc.line, 'line', equals(line))
+                .having((loc) => loc.column, 'column', greaterThan(column)));
+
+        // Remove breakpoint so it doesn't impact other tests.
+        await service.removeBreakpoint(isolate.id, bp.id);
+      });
     });
   });
 }
diff --git a/fixtures/_testPackage/web/main.dart b/fixtures/_testPackage/web/main.dart
index d966da7..0d82f4b 100644
--- a/fixtures/_testPackage/web/main.dart
+++ b/fixtures/_testPackage/web/main.dart
@@ -40,6 +40,7 @@
     printFromTestPackage();
     printCallExtension();
     printLoopVariable();
+    printNestedObjectsMultiLine();
   });
 
   document.body.appendText(concatenate('Program', ' is running!'));
@@ -99,6 +100,13 @@
   d.deferredPrintLocal();
 }
 
+void printNestedObjectsMultiLine() {
+  print(// Breakpoint: printEnclosingFunctionMultiLine
+      EnclosingMainClass(// Breakpoint: printEnclosingObjectMultiLine
+          MainClass(0) // Breakpoint: printNestedObjectMultiLine
+          ));
+}
+
 class MainClass {
   final int _field;
   MainClass(this._field);
@@ -106,3 +114,11 @@
   @override
   String toString() => '$_field';
 }
+
+class EnclosingMainClass {
+  final MainClass _field;
+  EnclosingMainClass(this._field);
+
+  @override
+  String toString() => '$_field';
+}
diff --git a/fixtures/_testPackageSound/web/main.dart b/fixtures/_testPackageSound/web/main.dart
index 54aeaa1..5305c96 100644
--- a/fixtures/_testPackageSound/web/main.dart
+++ b/fixtures/_testPackageSound/web/main.dart
@@ -40,6 +40,7 @@
     printCallExtension();
     printLoopVariable();
     printGeneric<int>(0);
+    printNestedObjectsMultiLine();
   });
 
   document.body?.appendText(concatenate('Program', ' is running!'));
@@ -108,6 +109,13 @@
   d.deferredPrintLocal();
 }
 
+void printNestedObjectsMultiLine() {
+  print(// Breakpoint: printEnclosingFunctionMultiLine
+      EnclosingMainClass(// Breakpoint: printEnclosingObjectMultiLine
+          MainClass(0) // Breakpoint: printNestedObjectMultiLine
+          ));
+}
+
 class MainClass {
   final int _field;
   MainClass(this._field);
@@ -115,3 +123,11 @@
   @override
   String toString() => '$_field';
 }
+
+class EnclosingMainClass {
+  final MainClass _field;
+  EnclosingMainClass(this._field);
+
+  @override
+  String toString() => '$_field';
+}