forked from dart-lang/dartdoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodel_node.dart
172 lines (146 loc) · 5.27 KB
/
model_node.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (c) 2019, 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:convert';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
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].
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, {
required Map<String, CommentReferenceData>? commentReferenceData,
}) {
if (sourceNode == null) {
return ModelNode._(element, analysisContext,
sourceEnd: -1, sourceOffset: -1);
} else {
// Get a node higher up the syntax tree that includes the semicolon.
// In this case, it is either a [FieldDeclaration] or
// [TopLevelVariableDeclaration]. (#2401)
if (sourceNode is VariableDeclaration) {
sourceNode = sourceNode.parent!.parent!;
assert(sourceNode is FieldDeclaration ||
sourceNode is TopLevelVariableDeclaration);
}
return ModelNode._(
element,
analysisContext,
sourceEnd: sourceNode.end,
sourceOffset: sourceNode.offset,
commentReferenceData: commentReferenceData,
);
}
}
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;
/// The text of the source code of this node, stripped of the leading
/// indentation, and stripped of the doc comments.
late final String sourceCode = () {
if (_isSynthetic) return '';
var path = _element.source?.fullName;
if (path == null) return '';
var fileResult = _analysisContext.currentSession.getFile(path);
if (fileResult is! FileResult) return '';
return fileResult.content
.substringFromLineStart(_sourceOffset, _sourceEnd)
.stripIndent
.stripDocComments
.trim();
}();
}
/// 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) {
var lineStartOffset = startOfLineWithOffset(offset);
return substring(lineStartOffset, endOffset);
}
// Finds the start of the line which contains the character at [offset].
int startOfLineWithOffset(int offset) {
var i = offset;
// Walk backwards until we find the previous line's EOL character.
while (i > 0) {
i -= 1;
if (this[i] == '\n' || this[i] == '\r') {
i += 1;
break;
}
}
return i;
}
/// Strips leading doc comments from the given source code.
String get stripDocComments {
var remainder = trimLeft();
var lineComments = remainder.startsWith(_tripleSlash) ||
remainder.startsWith(_escapedTripleSlash);
var blockComments = remainder.startsWith(_slashStarStar) ||
remainder.startsWith(_escapedSlashStarStar);
return split('\n').where((String line) {
if (lineComments) {
if (line.startsWith(_tripleSlash) ||
line.startsWith(_escapedTripleSlash)) {
return false;
}
lineComments = false;
return true;
} else if (blockComments) {
if (line.contains(_starSlash) || line.contains(_escapedStarSlash)) {
blockComments = false;
return false;
}
if (line.startsWith(_slashStarStar) ||
line.startsWith(_escapedSlashStarStar)) {
return false;
}
return false;
}
return true;
}).join('\n');
}
/// Strips the common indent from the given source fragment.
String get stripIndent {
var remainder = trimLeft();
var indent = substring(0, length - remainder.length);
return split('\n').map((line) {
line = line.trimRight();
return line.startsWith(indent) ? line.substring(indent.length) : line;
}).join('\n');
}
}
const HtmlEscape _escape = HtmlEscape();
const String _tripleSlash = '///';
final String _escapedTripleSlash = _escape.convert(_tripleSlash);
const String _slashStarStar = '/**';
final String _escapedSlashStarStar = _escape.convert(_slashStarStar);
const String _starSlash = '*/';
final String _escapedStarSlash = _escape.convert(_starSlash);