Skip to content

Commit d761fd7

Browse files
authored
Fix issues with source code HTML of fields and accessors (#2401)
* fix(fields,accessors): escape source code HTML * test: added tests * fix(model_node): add semicolon for fields * test: added tests * fix(model_node): less hacky way to solve the issue * test: added a test * code-style: fix comment style * refactor(model_node): rename variable for clarity
1 parent 36e3f68 commit d761fd7

File tree

5 files changed

+155
-10
lines changed

5 files changed

+155
-10
lines changed

lib/src/model/accessor.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:analyzer/dart/element/element.dart';
66
import 'package:analyzer/src/dart/element/member.dart' show Member;
77
import 'package:dartdoc/src/model/model.dart';
8+
import 'package:dartdoc/src/render/source_code_renderer.dart';
89
import 'package:dartdoc/src/utils.dart';
910
import 'package:dartdoc/src/warnings.dart';
1011

@@ -23,6 +24,9 @@ class Accessor extends ModelElement implements EnclosedElement {
2324

2425
bool get isSynthetic => element.isSynthetic;
2526

27+
SourceCodeRenderer get _sourceCodeRenderer =>
28+
packageGraph.rendererFactory.sourceCodeRenderer;
29+
2630
GetterSetterCombo _definingCombo;
2731
// The [enclosingCombo] where this element was defined.
2832
GetterSetterCombo get definingCombo {
@@ -40,8 +44,8 @@ class Accessor extends ModelElement implements EnclosedElement {
4044
String get sourceCode {
4145
if (_sourceCode == null) {
4246
if (isSynthetic) {
43-
_sourceCode =
44-
packageGraph.getModelNodeFor(definingCombo.element).sourceCode;
47+
_sourceCode = _sourceCodeRenderer.renderSourceCode(
48+
packageGraph.getModelNodeFor(definingCombo.element).sourceCode);
4549
} else {
4650
_sourceCode = super.sourceCode;
4751
}

lib/src/model/field.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/element.dart';
66
import 'package:analyzer/src/dart/element/element.dart';
77
import 'package:dartdoc/src/element_type.dart';
88
import 'package:dartdoc/src/model/model.dart';
9+
import 'package:dartdoc/src/render/source_code_renderer.dart';
910

1011
class Field extends ModelElement
1112
with GetterSetterCombo, ContainerMember, Inheritable
@@ -160,6 +161,9 @@ class Field extends ModelElement
160161
@override
161162
String get fileName => '${isConst ? '$name-constant' : name}.$fileType';
162163

164+
SourceCodeRenderer get _sourceCodeRenderer =>
165+
packageGraph.rendererFactory.sourceCodeRenderer;
166+
163167
String _sourceCode;
164168

165169
@override
@@ -171,6 +175,7 @@ class Field extends ModelElement
171175
var setterSourceCode = setter?.sourceCode ?? '';
172176
var buffer = StringBuffer();
173177
if (fieldSourceCode.isNotEmpty) {
178+
fieldSourceCode = _sourceCodeRenderer.renderSourceCode(fieldSourceCode);
174179
buffer.write(fieldSourceCode);
175180
}
176181
if (buffer.isNotEmpty) buffer.write('\n\n');

lib/src/model/model_node.dart

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ class ModelNode {
2525
final Element element;
2626
final ResourceProvider resourceProvider;
2727

28-
final int _sourceOffset;
29-
final int _sourceEnd;
28+
final AstNode _sourceNode;
3029

3130
ModelNode(AstNode sourceNode, this.element, this.resourceProvider)
32-
: _sourceOffset = sourceNode?.offset,
33-
_sourceEnd = sourceNode?.end,
31+
: _sourceNode = sourceNode,
3432
commentRefs = _commentRefsFor(sourceNode);
3533

3634
static List<ModelCommentReference> _commentRefsFor(AstNode node) {
@@ -47,11 +45,25 @@ class ModelNode {
4745

4846
String get sourceCode {
4947
if (_sourceCode == null) {
50-
if (_sourceOffset != null) {
48+
if (_sourceNode?.offset != null) {
49+
var enclosingSourceNode = _sourceNode;
50+
51+
/// Get a node higher up the syntax tree that includes the semicolon.
52+
/// In this case, it is either a [FieldDeclaration] or
53+
/// [TopLevelVariableDeclaration]. (#2401)
54+
if (_sourceNode is VariableDeclaration) {
55+
enclosingSourceNode = _sourceNode.parent.parent;
56+
assert(enclosingSourceNode is FieldDeclaration ||
57+
enclosingSourceNode is TopLevelVariableDeclaration);
58+
}
59+
60+
var sourceEnd = enclosingSourceNode.end;
61+
var sourceOffset = enclosingSourceNode.offset;
62+
5163
var contents =
5264
model_utils.getFileContentsFor(element, resourceProvider);
5365
// Find the start of the line, so that we can line up all the indents.
54-
var i = _sourceOffset;
66+
var i = sourceOffset;
5567
while (i > 0) {
5668
i -= 1;
5769
if (contents[i] == '\n' || contents[i] == '\r') {
@@ -61,8 +73,8 @@ class ModelNode {
6173
}
6274

6375
// Trim the common indent from the source snippet.
64-
var start = _sourceOffset - (_sourceOffset - i);
65-
var source = contents.substring(start, _sourceEnd);
76+
var start = sourceOffset - (sourceOffset - i);
77+
var source = contents.substring(start, sourceEnd);
6678

6779
source = model_utils.stripIndentFromSource(source);
6880
source = model_utils.stripDartdocCommentsFromSource(source);

test/end2end/model_test.dart

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3764,6 +3764,85 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
37643764
});
37653765
});
37663766

3767+
group('Source Code HTML', () {
3768+
Class EscapableProperties;
3769+
Field implicitGetterExplicitSetter, explicitGetterImplicitSetter;
3770+
Field explicitGetterSetter, explicitGetterSetterForInheriting;
3771+
Field finalProperty, simpleProperty, forInheriting;
3772+
Field ensureWholeDeclarationIsVisible;
3773+
3774+
setUpAll(() {
3775+
EscapableProperties = fakeLibrary.classes
3776+
.firstWhere((c) => c.name == 'HtmlEscapableProperties');
3777+
implicitGetterExplicitSetter = EscapableProperties.allModelElements
3778+
.firstWhere((e) => e.name == 'implicitGetterExplicitSetter');
3779+
explicitGetterImplicitSetter = EscapableProperties.allModelElements
3780+
.firstWhere((e) => e.name == 'explicitGetterImplicitSetter');
3781+
explicitGetterSetter = EscapableProperties.allModelElements
3782+
.firstWhere((e) => e.name == 'explicitGetterSetter');
3783+
finalProperty = EscapableProperties.allModelElements
3784+
.firstWhere((e) => e.name == 'finalProperty');
3785+
simpleProperty = EscapableProperties.allModelElements
3786+
.firstWhere((e) => e.name == 'simpleProperty');
3787+
forInheriting = EscapableProperties.allModelElements
3788+
.firstWhere((e) => e.name == 'forInheriting');
3789+
explicitGetterSetterForInheriting = EscapableProperties.allModelElements
3790+
.firstWhere((e) => e.name == 'explicitGetterSetterForInheriting');
3791+
ensureWholeDeclarationIsVisible = EscapableProperties.allModelElements
3792+
.firstWhere((e) => e.name == 'ensureWholeDeclarationIsVisible');
3793+
});
3794+
3795+
test('Normal property fields are escaped', () {
3796+
expect(finalProperty.sourceCode, contains('&lt;int&gt;'));
3797+
expect(simpleProperty.sourceCode, contains('&lt;int&gt;'));
3798+
expect(forInheriting.sourceCode, contains('&lt;int&gt;'));
3799+
});
3800+
3801+
test('Explicit accessors are escaped', () {
3802+
expect(explicitGetterSetter.getter.sourceCode, contains('&lt;int&gt;'));
3803+
expect(explicitGetterSetter.setter.sourceCode, contains('&lt;int&gt;'));
3804+
});
3805+
3806+
test('Implicit accessors are escaped', () {
3807+
expect(implicitGetterExplicitSetter.getter.sourceCode,
3808+
contains('&lt;int&gt;'));
3809+
expect(implicitGetterExplicitSetter.setter.sourceCode,
3810+
contains('&lt;int&gt;'));
3811+
expect(explicitGetterImplicitSetter.getter.sourceCode,
3812+
contains('&lt;int&gt;'));
3813+
expect(explicitGetterImplicitSetter.setter.sourceCode,
3814+
contains('&lt;int&gt;'));
3815+
expect(explicitGetterSetterForInheriting.getter.sourceCode,
3816+
contains('&lt;int&gt;'));
3817+
expect(explicitGetterSetterForInheriting.setter.sourceCode,
3818+
contains('&lt;int&gt;'));
3819+
});
3820+
3821+
test('Property fields are terminated with semicolon', () {
3822+
expect(finalProperty.sourceCode.trim(), endsWith('List&lt;int&gt;();'));
3823+
expect(simpleProperty.sourceCode.trim(), endsWith('List&lt;int&gt;();'));
3824+
expect(forInheriting.sourceCode.trim(), endsWith('forInheriting;'));
3825+
});
3826+
3827+
test('Arrow accessors are terminated with semicolon', () {
3828+
expect(explicitGetterImplicitSetter.getter.sourceCode.trim(),
3829+
endsWith('List&lt;int&gt;();'));
3830+
expect(explicitGetterSetter.getter.sourceCode.trim(),
3831+
endsWith('List&lt;int&gt;();'));
3832+
});
3833+
3834+
test('Traditional accessors are not terminated with semicolon', () {
3835+
expect(implicitGetterExplicitSetter.setter.sourceCode.trim(),
3836+
endsWith('\{\}'));
3837+
expect(explicitGetterSetter.setter.sourceCode.trim(), endsWith('\{\}'));
3838+
});
3839+
3840+
test('Whole declaration is visible when declaration spans many lines', () {
3841+
expect(ensureWholeDeclarationIsVisible.sourceCode,
3842+
contains('List&lt;int&gt; '));
3843+
});
3844+
});
3845+
37673846
group('Sorting by name', () {
37683847
// Order by uppercased lexical ordering for non-digits,
37693848
// lexicographical ordering of embedded digit sequences.

testing/test_package/lib/fake.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,51 @@ class ClassWithUnusualProperties extends ImplicitProperties {
564564
}
565565
}
566566

567+
class HtmlEscapableImplicitProperties {
568+
/// Docs for implicitGetterExplicitSetter from HtmlEscapableImplicitProperties.
569+
List<int> implicitGetterExplicitSetter;
570+
571+
/// Docs for explicitGetterImplicitSetter from HtmlEscapableImplicitProperties.
572+
List<int> explicitGetterImplicitSetter;
573+
574+
/// A simple property to inherit.
575+
List<int> forInheriting;
576+
577+
/// Explicit getter for inheriting.
578+
List<int> get explicitGetterSetterForInheriting => [1, 2];
579+
580+
/// Explicit setter for inheriting.
581+
set explicitGetterSetterForInheriting(List<int> foo) {}
582+
}
583+
584+
class HtmlEscapableProperties extends HtmlEscapableImplicitProperties {
585+
@override
586+
587+
/// Docs for setter of implicitGetterExplicitSetter.
588+
set implicitGetterExplicitSetter(List<int> x) {}
589+
590+
@override
591+
592+
/// Getter doc for explicitGetterImplicitSetter
593+
List<int> get explicitGetterImplicitSetter => List<int>();
594+
595+
/// Getter doc for explicitGetterSetter.
596+
List<int> get explicitGetterSetter => List<int>();
597+
598+
/// Setter doc for explicitGetterSetter.
599+
set explicitGetterSetter(List<int> aList) {}
600+
601+
/// A final property.
602+
final List<int> finalProperty = List<int>();
603+
604+
/// A simple property.
605+
List<int> simpleProperty = List<int>();
606+
607+
/// A long multiple variable declaration.
608+
List<int> iAmALongLongLongLongLongLongLongLongLongName,
609+
ensureWholeDeclarationIsVisible;
610+
}
611+
567612
/// This is a very long line spread
568613
/// across... wait for it... two physical lines.
569614
///

0 commit comments

Comments
 (0)