Skip to content

Commit

Permalink
Store comment reference data on ModelNode
Browse files Browse the repository at this point in the history
  • Loading branch information
srawlins committed Jun 26, 2024
1 parent 7ed6ef8 commit a81e492
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 63 deletions.
44 changes: 37 additions & 7 deletions lib/src/model/model_node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:meta/meta.dart';

/// Stripped down information derived from [AstNode] containing only information
/// needed to resurrect the source code of [element].
/// needed to resurrect the source code of [_element].
class ModelNode {
final Element _element;
final AnalysisContext _analysisContext;
final int _sourceEnd;
final int _sourceOffset;

/// Data about each comment reference found in the doc comment of this node.
final Map<String, CommentReferenceData>? commentReferenceData;

factory ModelNode(
AstNode? sourceNode, Element element, AnalysisContext analysisContext) {
AstNode? sourceNode,
Element element,
AnalysisContext analysisContext, {
required Map<String, CommentReferenceData>? commentReferenceData,
}) {
if (sourceNode == null) {
return ModelNode._(element, analysisContext,
sourceEnd: -1, sourceOffset: -1);
Expand All @@ -32,14 +39,23 @@ class ModelNode {
assert(sourceNode is FieldDeclaration ||
sourceNode is TopLevelVariableDeclaration);
}
return ModelNode._(element, analysisContext,
sourceEnd: sourceNode.end, sourceOffset: sourceNode.offset);
return ModelNode._(
element,
analysisContext,
sourceEnd: sourceNode.end,
sourceOffset: sourceNode.offset,
commentReferenceData: commentReferenceData,
);
}
}

ModelNode._(this._element, this._analysisContext,
{required int sourceEnd, required int sourceOffset})
: _sourceEnd = sourceEnd,
ModelNode._(
this._element,
this._analysisContext, {
required int sourceEnd,
required int sourceOffset,
this.commentReferenceData = const {},
}) : _sourceEnd = sourceEnd,
_sourceOffset = sourceOffset;

bool get _isSynthetic => _sourceEnd < 0 || _sourceOffset < 0;
Expand All @@ -63,6 +79,20 @@ class ModelNode {
}();
}

/// Comment reference data from the syntax tree.
///
/// Comment reference data is not available on the analyzer's Element model, so
/// we store it after resolving libraries in instances of this class.
class CommentReferenceData {
final Element element;
final String name;
final int offset;
final int length;

CommentReferenceData(this.element, String? name, this.offset, this.length)
: name = name ?? '';
}

@visibleForTesting
extension SourceStringExtensions on String {
String substringFromLineStart(int offset, int endOffset) {
Expand Down
87 changes: 83 additions & 4 deletions lib/src/model/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import 'dart:collection';

import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/source.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/ast/ast.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/element/inheritance_manager3.dart'
show InheritanceManager3;
// ignore: implementation_imports
Expand Down Expand Up @@ -234,6 +235,20 @@ class PackageGraph with CommentReferable, Nameable {
// me how, because the data is on AST nodes, not the element model.
void gatherModelNodes(DartDocResolvedLibrary resolvedLibrary) {
for (var unit in resolvedLibrary.units) {
for (var directive in unit.directives.whereType<LibraryDirective>()) {
// There should be only one library directive. If there are more, there
// is no harm in grabbing ModelNode for each.
var commentReferenceData = directive.documentationComment?.data;
_modelNodes.putIfAbsent(
resolvedLibrary.element,
() => ModelNode(
directive,
resolvedLibrary.element,
analysisContext,
commentReferenceData: commentReferenceData,
));
}

for (var declaration in unit.declarations) {
_populateModelNodeFor(declaration);
switch (declaration) {
Expand All @@ -243,6 +258,9 @@ class PackageGraph with CommentReferable, Nameable {
}
case EnumDeclaration():
if (declaration.declaredElement?.isPublic ?? false) {
for (var constant in declaration.constants) {
_populateModelNodeFor(constant);
}
for (var member in declaration.members) {
_populateModelNodeFor(member);
}
Expand All @@ -269,12 +287,21 @@ class PackageGraph with CommentReferable, Nameable {
}

void _populateModelNodeFor(Declaration declaration) {
var commentReferenceData = declaration.documentationComment?.data;

if (declaration is FieldDeclaration) {
var fields = declaration.fields.variables;
for (var field in fields) {
var element = field.declaredElement!;
_modelNodes.putIfAbsent(
element, () => ModelNode(field, element, analysisContext));
element,
() => ModelNode(
field,
element,
analysisContext,
commentReferenceData: commentReferenceData,
),
);
}
return;
}
Expand All @@ -283,13 +310,27 @@ class PackageGraph with CommentReferable, Nameable {
for (var field in fields) {
var element = field.declaredElement!;
_modelNodes.putIfAbsent(
element, () => ModelNode(field, element, analysisContext));
element,
() => ModelNode(
field,
element,
analysisContext,
commentReferenceData: commentReferenceData,
),
);
}
return;
}
var element = declaration.declaredElement!;
_modelNodes.putIfAbsent(
element, () => ModelNode(declaration, element, analysisContext));
element,
() => ModelNode(
declaration,
element,
analysisContext,
commentReferenceData: commentReferenceData,
),
);
}

ModelNode? getModelNodeFor(Element element) => _modelNodes[element];
Expand Down Expand Up @@ -1029,3 +1070,41 @@ class InheritableElementsKey {

InheritableElementsKey(this.element, this.library);
}

extension on Comment {
/// A mapping of all comment references to their various data.
Map<String, CommentReferenceData> get data {
if (references.isEmpty) return const {};

var data = <String, CommentReferenceData>{};
for (var reference in references) {
var commentReferable = reference.expression;
String name;
Element? staticElement;
if (commentReferable case PropertyAccessImpl(:var propertyName)) {
var target = commentReferable.target;
if (target is! PrefixedIdentifierImpl) continue;
name = '${target.name}.${propertyName.name}';
staticElement = propertyName.staticElement;
} else if (commentReferable case PrefixedIdentifier(:var identifier)) {
name = commentReferable.name;
staticElement = identifier.staticElement;
} else if (commentReferable case SimpleIdentifier()) {
name = commentReferable.name;
staticElement = commentReferable.staticElement;
} else {
continue;
}

if (staticElement != null && !data.containsKey(name)) {
data[name] = CommentReferenceData(
staticElement,
name,
commentReferable.offset,
commentReferable.length,
);
}
}
return data;
}
}
7 changes: 7 additions & 0 deletions test/dartdoc_test_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,11 @@ $libraryContent
);
return await Dartdoc.fromContext(context, packageBuilder);
}

/// The real offset in a library generated with [bootPackageWithLibrary].
///
/// When a library is written via [bootPackageWithLibrary], the test author
/// provides `libraryContent`, which is a snippet of Dart library text.
int realOffsetFor(int offsetInContent) =>
'\n\nlibrary $libraryName\n\n'.length + offsetInContent;
}
Loading

0 comments on commit a81e492

Please sign in to comment.