| // 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); |
| } |
| } |