blob: 0b4829f9379cb37fe6f1d52fcb8a07a64e8f6aa8 [file] [log] [blame]
// Copyright (c) 2024, 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 'dart:math' as math;
import 'package:kernel/ast.dart';
// Coverage-ignore(suite): Not run.
/// Class that maps offsets from an intermediate augmentation library to the
/// merged augmentation library.
class ReOffset {
final Uri intermediateAugmentationUri;
final Uri augmentationFileUri;
final List<MapEntry<int, int?>> _offsets;
/// Creates a [ReOffset] from the intermediate augmentation library
/// [intermediateAugmentationUri] to the merged augmentation library
/// [augmentationFileUri] using [reOffsetMap] which maps the start of an
/// offset range in [intermediateAugmentationUri] to the start of the
/// corresponding offset range in [augmentationFileUri].
///
/// The keys of [reOffsetMap] are assumed to be sorted.
ReOffset(this.intermediateAugmentationUri, this.augmentationFileUri,
Map<int, int?> reOffsetMap)
: _offsets = reOffsetMap.entries.toList(),
assert(() {
Iterator<int> iterator = reOffsetMap.keys.iterator;
if (iterator.moveNext()) {
int previous = iterator.current;
while (iterator.moveNext()) {
int next = iterator.current;
if (next < previous) {
return false;
}
previous = next;
}
}
return true;
}(), "Offset key must be sorted: ${reOffsetMap}");
/// Computes the file offset in the merged augmentation library corresponding
/// to the [fileOffset] from the intermediate augmentation library.
int reOffset(int fileOffset) {
int low = 0;
int high = _offsets.length - 1;
while (low < high) {
int mid = high - ((high - low) >> 1); // Get middle, rounding up.
int midOffset = _offsets[mid].key;
if (midOffset <= fileOffset) {
low = mid;
} else {
high = mid - 1;
}
}
int intermediateAugmentationRangeStart = _offsets[low].key;
int? mergedAugmentationRangeStart = _offsets[low].value;
/// We compute the new offset by adding the relative distance to start of
/// the old offset range to the start of the new offset range.
// TODO(johnniwinther): Verify that this doesn't lead to offsets that
// escape their original range.
if (mergedAugmentationRangeStart != null) {
return fileOffset -
intermediateAugmentationRangeStart +
mergedAugmentationRangeStart;
}
assert(false,
"No offset found for $fileOffset in $intermediateAugmentationUri.");
return TreeNode.noOffset;
}
}
// Coverage-ignore(suite): Not run.
/// Recursive visitor that tracks the current file URI.
abstract class FileUriVisitor extends RecursiveVisitor {
/// Called before the `FileUriNode` [node] is visited.
void enterFileUri(FileUriNode node);
/// Called after the `FileUriNode` [node] is visited.
void exitFileUri(FileUriNode node);
void handleClass(Class node) {}
@override
void visitClass(Class node) {
enterFileUri(node);
handleClass(node);
super.visitClass(node);
exitFileUri(node);
}
void handleConstructor(Constructor node) {}
@override
void visitConstructor(Constructor node) {
enterFileUri(node);
handleConstructor(node);
super.visitConstructor(node);
exitFileUri(node);
}
void handleExtension(Extension node) {}
@override
void visitExtension(Extension node) {
enterFileUri(node);
handleExtension(node);
super.visitExtension(node);
exitFileUri(node);
}
void handleExtensionTypeDeclaration(ExtensionTypeDeclaration node) {}
@override
void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
enterFileUri(node);
handleExtensionTypeDeclaration(node);
super.visitExtensionTypeDeclaration(node);
exitFileUri(node);
}
void handleField(Field node) {}
@override
void visitField(Field node) {
enterFileUri(node);
handleField(node);
super.visitField(node);
exitFileUri(node);
}
void handleFileUriExpression(FileUriExpression node) {}
@override
void visitFileUriExpression(FileUriExpression node) {
enterFileUri(node);
handleFileUriExpression(node);
super.visitFileUriExpression(node);
exitFileUri(node);
}
void handleFileUriConstantExpression(FileUriConstantExpression node) {}
@override
void visitConstantExpression(ConstantExpression node) {
if (node is FileUriConstantExpression) {
enterFileUri(node);
handleFileUriConstantExpression(node);
super.visitConstantExpression(node);
exitFileUri(node);
} else {
super.visitConstantExpression(node);
}
}
void handleLibrary(Library node) {}
@override
void visitLibrary(Library node) {
enterFileUri(node);
handleLibrary(node);
super.visitLibrary(node);
exitFileUri(node);
}
void handleProcedure(Procedure node) {}
@override
void visitProcedure(Procedure node) {
enterFileUri(node);
handleProcedure(node);
super.visitProcedure(node);
exitFileUri(node);
}
void handleTypedef(Typedef node) {}
@override
void visitTypedef(Typedef node) {
enterFileUri(node);
handleTypedef(node);
super.visitTypedef(node);
exitFileUri(node);
}
}
// Coverage-ignore(suite): Not run.
/// Visitor that replaces offsets in intermediate augmentation libraries with
/// the offsets for the merged augmentation libraries.
class ReOffsetVisitor extends FileUriVisitor {
final Map<Uri, ReOffset> _reOffsetMaps;
final List<ReOffset> _currentReOffsets = [];
ReOffsetVisitor(this._reOffsetMaps);
int _reOffset(int offset) {
if (_currentReOffsets.isNotEmpty) {
if (offset != TreeNode.noOffset) {
return _currentReOffsets.last.reOffset(offset);
}
}
return offset;
}
@override
void enterFileUri(FileUriNode node) {
ReOffset? reOffset = _reOffsetMaps[node.fileUri];
if (reOffset != null) {
_currentReOffsets.add(reOffset);
}
}
@override
void exitFileUri(FileUriNode node) {
ReOffset? reOffset = _reOffsetMaps[node.fileUri];
if (reOffset != null) {
node.fileUri = reOffset.augmentationFileUri;
_currentReOffsets.removeLast();
}
}
@override
void defaultTreeNode(TreeNode node) {
node.fileOffset = _reOffset(node.fileOffset);
super.defaultTreeNode(node);
}
@override
void visitAssertStatement(AssertStatement node) {
node.conditionStartOffset = _reOffset(node.conditionStartOffset);
node.conditionEndOffset = _reOffset(node.conditionEndOffset);
super.visitAssertStatement(node);
}
@override
void visitBlock(Block node) {
node.fileEndOffset = _reOffset(node.fileEndOffset);
super.visitBlock(node);
}
@override
void handleClass(Class node) {
node.startFileOffset = _reOffset(node.startFileOffset);
node.fileEndOffset = _reOffset(node.fileEndOffset);
}
@override
void handleConstructor(Constructor node) {
node.startFileOffset = _reOffset(node.startFileOffset);
node.fileEndOffset = _reOffset(node.fileEndOffset);
}
@override
void visitForInStatement(ForInStatement node) {
node.bodyOffset = _reOffset(node.bodyOffset);
super.visitForInStatement(node);
}
@override
void visitFunctionNode(FunctionNode node) {
node.fileEndOffset = _reOffset(node.fileEndOffset);
super.visitFunctionNode(node);
}
@override
void handleField(Field node) {
node.fileEndOffset = _reOffset(node.fileEndOffset);
}
@override
void handleProcedure(Procedure node) {
node.fileStartOffset = _reOffset(node.fileStartOffset);
node.fileEndOffset = _reOffset(node.fileEndOffset);
}
@override
void visitSwitchCase(SwitchCase node) {
for (int i = 0; i < node.expressionOffsets.length; i++) {
node.expressionOffsets[i] = _reOffset(node.expressionOffsets[i]);
}
super.visitSwitchCase(node);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
node.fileEqualsOffset = _reOffset(node.fileEqualsOffset);
super.visitVariableDeclaration(node);
}
}
// Coverage-ignore(suite): Not run.
/// A range of file offset.
///
/// Used to computed the file offsets of nested ranges.
class OffsetRange {
final int start;
final int end;
OffsetRange(this.start, this.end);
OffsetRange include(OffsetRange range) {
return new OffsetRange(
math.min(start, range.start), math.max(end, range.end));
}
}
// Coverage-ignore(suite): Not run.
extension OffsetRangeExtension on OffsetRange? {
OffsetRange include(OffsetRange range) {
OffsetRange? self = this;
return self == null ? range : self.include(range);
}
}