| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:analysis_server/lsp_protocol/protocol.dart' |
| show Position, Range; |
| import 'package:analysis_server/src/lsp/mapping.dart'; |
| import 'package:analyzer/source/line_info.dart'; |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; |
| import 'package:collection/collection.dart'; |
| import 'package:test/test.dart' show expect; |
| |
| extension ListTestCodePositionExtension on List<TestCodePosition> { |
| /// Return the LSP [Position]s of the markers. |
| /// |
| /// Positions are based on [TestCode.code], with all parsed markers removed. |
| List<Position> get positions => map((position) => position.position).toList(); |
| } |
| |
| extension ListTestCodeRangeExtension on List<TestCodeRange> { |
| /// The LSP [Range]s indicated by the markers. |
| /// |
| /// Ranges are based on [TestCode.code], with all parsed markers removed. |
| List<Range> get ranges => map((range) => range.range).toList(); |
| } |
| |
| extension TestCodeExtension on TestCode { |
| /// Return the offsets of all [positions]. |
| List<int> get positionOffsets => positions.map((p) => p.offset).toList(); |
| |
| /// Verifies that [actualRanges] match with the marked ranges in [code]. |
| /// |
| /// This is done by taking the resulting code (without markers) and then |
| /// inserting the range markers from [actualRanges] and performing a string |
| /// comparison. |
| /// |
| /// This can result in a simpler failure message/diff than having the JSON of |
| /// a large [List<Range>] dumped. |
| void verifyRanges(Iterable<Range> actualRanges) { |
| // First rewrite the marked code to have only ranges, and using the indexed |
| // markers. This removes any `^` we won't be verifing and changes any |
| // range shorthand to indexed tokens. |
| var expected = _withRanges(ranges.map((range) => range.range)); |
| |
| // Now build the same with the actual result ranges. |
| var actual = _withRanges(actualRanges); |
| |
| expect(actual, expected); |
| } |
| |
| /// Replaces all marked positions and ranges with [ranges]. |
| String _withRanges(Iterable<Range> newRanges) { |
| var insertedCharacters = 0; |
| var rangeIndex = 0; |
| var newContent = code; // Start from the unmarked code. |
| var lineInfo = LineInfo.fromContent(newContent); |
| |
| /// Helper to insert the markers for [range] into the content. |
| void markRange(Range range) { |
| /// Helper to insert [text] at [offset] in the content. |
| void insertText(int offset, String text) { |
| var adjustedOffset = insertedCharacters + offset; |
| newContent = newContent.replaceRange( |
| adjustedOffset, |
| adjustedOffset, |
| text, |
| ); |
| insertedCharacters += text.length; |
| } |
| |
| var startOffset = toOffset(lineInfo, range.start).result; |
| var endOffset = toOffset(lineInfo, range.end).result; |
| insertText(startOffset, '/*[$rangeIndex*/'); |
| insertText(endOffset, '/*$rangeIndex]*/'); |
| rangeIndex++; |
| } |
| |
| var sortedRanges = newRanges.sortedBy( |
| (range) => toOffset(lineInfo, range.start).result, |
| ); |
| sortedRanges.forEach(markRange); |
| |
| return newContent; |
| } |
| } |
| |
| extension TestCodePositionExtension on TestCodePosition { |
| /// Return the LSP [Position] of the marker. |
| /// |
| /// Positions are based on [TestCode.code], with all parsed markers removed. |
| Position get position => toPosition(lineInfo.getLocation(offset)); |
| } |
| |
| extension TestCodeRangeExtension on TestCodeRange { |
| /// The LSP [Range] indicated by the markers. |
| /// |
| /// Ranges are based on [TestCode.code], with all parsed markers removed. |
| Range get range => toRange(lineInfo, sourceRange.offset, sourceRange.length); |
| } |