Skip to content

Commit 52c6739

Browse files
committed
Add ability to "Crossdartify" the source code
If there's a crossdart.json file in the input dir (which can be generated by Crossdart), it will use that file to add links to crossdart.info in the source code block. Since now we should support links in the source code section, I removed `<code>` wrapping tags from it, and also now I HTML escape the source code, and use triple mustache curly braces (`{{{sourceCode}}}`) to insert it to the page. Hopefully, that will simplify developer's life, when they want to look into the method's implementation.
1 parent cf2fe2e commit 52c6739

File tree

7 files changed

+143
-18
lines changed

7 files changed

+143
-18
lines changed

bin/dartdoc.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ main(List<String> arguments) async {
123123
var addCrossdart = args['add-crossdart'] as bool;
124124
var includeSource = args['include-source'] as bool;
125125

126-
initializeConfig(addCrossdart: addCrossdart, includeSource: includeSource);
126+
initializeConfig(
127+
addCrossdart: addCrossdart,
128+
includeSource: includeSource,
129+
inputDir: inputDir);
127130

128131
var dartdoc = new DartDoc(inputDir, excludeLibraries, sdkDir, generators,
129132
outputDir, packageMeta, includeLibraries,

lib/resources/styles.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,16 @@ h2 .crossdart {
178178
margin-top: 1em;
179179
}
180180

181+
.crossdart-link {
182+
border-bottom: 1px solid #dfdfdf;
183+
text-decoration: none;
184+
}
185+
186+
.crossdart-link:hover {
187+
border-bottom: 1px solid #aaa;
188+
text-decoration: none;
189+
}
190+
181191
@media(max-width: 768px) {
182192
nav .container {
183193
width: 100%

lib/src/config.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
library dartdoc.config;
22

3+
import 'dart:io';
4+
35
class Config {
6+
final Directory inputDir;
47
final bool addCrossdart;
58
final bool includeSource;
6-
Config._(this.addCrossdart, this.includeSource);
9+
Config._(this.inputDir, this.addCrossdart, this.includeSource);
710
}
811

912
Config _config;
1013
Config get config => _config;
1114

12-
void initializeConfig({bool addCrossdart: false, bool includeSource: true}) {
13-
_config = new Config._(addCrossdart, includeSource);
15+
void initializeConfig(
16+
{Directory inputDir, bool addCrossdart: false, bool includeSource: true}) {
17+
_config = new Config._(inputDir, addCrossdart, includeSource);
1418
}

lib/src/model.dart

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,6 +1926,24 @@ class Parameter extends ModelElement implements EnclosedElement {
19261926
String toString() => element.name;
19271927
}
19281928

1929+
Map<String, Map<String, List<Map<String, dynamic>>>> __crossdartJson;
1930+
Map<String, Map<String, List<Map<String, dynamic>>>> get _crossdartJson {
1931+
if (__crossdartJson == null) {
1932+
if (config != null) {
1933+
var crossdartFile =
1934+
new File(p.join(config.inputDir.path, "crossdart.json"));
1935+
if (crossdartFile.existsSync()) {
1936+
__crossdartJson = JSON.decode(crossdartFile.readAsStringSync());
1937+
} else {
1938+
__crossdartJson = {};
1939+
}
1940+
} else {
1941+
__crossdartJson = {};
1942+
}
1943+
}
1944+
return __crossdartJson;
1945+
}
1946+
19291947
abstract class SourceCodeMixin {
19301948
String _sourceCodeCache;
19311949
String get crossdartHtmlTag {
@@ -1942,6 +1960,10 @@ abstract class SourceCodeMixin {
19421960

19431961
Library get library;
19441962

1963+
void clearSourceCodeCache() {
1964+
_sourceCodeCache = null;
1965+
}
1966+
19451967
String get sourceCode {
19461968
if (_sourceCodeCache == null) {
19471969
String contents = getFileContentsFor(element);
@@ -1958,8 +1980,14 @@ abstract class SourceCodeMixin {
19581980
}
19591981

19601982
// Trim the common indent from the source snippet.
1961-
String source =
1962-
contents.substring(node.offset - (node.offset - i), node.end);
1983+
var start = node.offset - (node.offset - i);
1984+
String source = contents.substring(start, node.end);
1985+
1986+
if (config != null && config.addCrossdart) {
1987+
source = crossdartifySource(_crossdartJson, source, element, start);
1988+
} else {
1989+
source = const HtmlEscape().convert(source);
1990+
}
19631991
source = stripIndentFromSource(source);
19641992
source = stripDartdocCommentsFromSource(source);
19651993

@@ -1976,14 +2004,18 @@ abstract class SourceCodeMixin {
19762004
if (_lineNumber != null && _sourceFilePath != null) {
19772005
String packageName = library.package.isSdk ? "sdk" : library.package.name;
19782006
String packageVersion = library.package.version;
1979-
var root = "${library.package.packageMeta.resolvedDir}/lib"
1980-
.replaceAll("\\", "/");
1981-
var sourceFilePath = _sourceFilePath
2007+
var root = library.package.packageMeta.resolvedDir;
2008+
if (!library.package.isSdk) {
2009+
root += "/lib";
2010+
}
2011+
root = root.replaceAll("\\", "/");
2012+
var sourceFilePath = new File(_sourceFilePath)
2013+
.resolveSymbolicLinksSync()
19822014
.replaceAll("\\", "/")
19832015
.replaceAll(root, "")
19842016
.replaceAll(new RegExp(r"^/*"), "");
19852017
String url =
1986-
"//crossdart.info/p/$packageName/$packageVersion/$sourceFilePath.html";
2018+
"//www.crossdart.info/p/$packageName/$packageVersion/$sourceFilePath.html";
19872019
return "${url}#line-${_lineNumber}";
19882020
} else {
19892021
return null;

lib/src/model_utils.dart

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
library dartdoc.model_utils;
66

77
import 'dart:io';
8+
import 'dart:convert';
89

910
import 'package:analyzer/dart/element/element.dart';
1011
import 'package:analyzer/dart/element/type.dart';
1112
import 'package:analyzer/src/generated/engine.dart';
1213
import 'package:analyzer/src/generated/sdk.dart';
1314
import 'package:analyzer/src/generated/source_io.dart';
15+
import 'package:path/path.dart' as p;
16+
import 'config.dart';
1417

1518
final Map<String, String> _fileContents = <String, String>{};
1619

@@ -79,20 +82,27 @@ bool isPublic(Element e) {
7982
/// Strip leading dartdoc comments from the given source code.
8083
String stripDartdocCommentsFromSource(String source) {
8184
String remainer = source.trimLeft();
82-
bool lineComments = remainer.startsWith('///');
83-
bool blockComments = remainer.startsWith('/**');
85+
HtmlEscape sanitizer = const HtmlEscape();
86+
bool lineComments = remainer.startsWith('///') ||
87+
remainer.startsWith(sanitizer.convert('///'));
88+
bool blockComments = remainer.startsWith('/**') ||
89+
remainer.startsWith(sanitizer.convert('/**'));
8490

8591
return source.split('\n').where((String line) {
8692
if (lineComments) {
87-
if (line.startsWith('///')) return false;
93+
if (line.startsWith('///') || line.startsWith(sanitizer.convert('///'))) {
94+
return false;
95+
}
8896
lineComments = false;
8997
return true;
9098
} else if (blockComments) {
91-
if (line.contains('*/')) {
99+
if (line.contains('*/') || line.contains(sanitizer.convert('*/'))) {
92100
blockComments = false;
93101
return false;
94102
}
95-
if (line.startsWith('/**')) return false;
103+
if (line.startsWith('/**') || line.startsWith(sanitizer.convert('/**'))) {
104+
return false;
105+
}
96106
return false;
97107
}
98108

@@ -109,3 +119,45 @@ String stripIndentFromSource(String source) {
109119
return line.startsWith(indent) ? line.substring(indent.length) : line;
110120
}).join('\n');
111121
}
122+
123+
/// Add links to crossdart.info to the given source fragment
124+
String crossdartifySource(
125+
Map<String, Map<String, List<Map<String, dynamic>>>> json,
126+
String source,
127+
Element element,
128+
int start) {
129+
var sanitizer = const HtmlEscape();
130+
String newSource;
131+
if (json.isNotEmpty) {
132+
var node = element.computeNode();
133+
var file =
134+
element.source.fullName.replaceAll("${config.inputDir.path}/", "");
135+
var filesData = json[file];
136+
if (filesData != null) {
137+
var data = filesData["references"]
138+
.where((r) => r["offset"] >= start && r["end"] <= node.end);
139+
if (data.isNotEmpty) {
140+
var previousStop = 0;
141+
var stringBuffer = new StringBuffer();
142+
for (var item in data) {
143+
stringBuffer.write(sanitizer
144+
.convert(source.substring(previousStop, item["offset"] - start)));
145+
stringBuffer
146+
.write("<a class='crossdart-link' href='${item["remotePath"]}'>");
147+
stringBuffer.write(sanitizer.convert(
148+
source.substring(item["offset"] - start, item["end"] - start)));
149+
stringBuffer.write("</a>");
150+
previousStop = item["end"] - start;
151+
}
152+
stringBuffer.write(
153+
sanitizer.convert(source.substring(previousStop, source.length)));
154+
155+
newSource = stringBuffer.toString();
156+
}
157+
}
158+
}
159+
if (newSource == null) {
160+
newSource = sanitizer.convert(source);
161+
}
162+
return newSource;
163+
}

lib/templates/_source_code.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{#hasSourceCode}}
22
<section class="summary source-code" id="source">
33
<h2><span>Source</span> {{{crossdartHtmlTag}}}</h2>
4-
<pre><code class="prettyprint lang-dart">{{ sourceCode }}</code></pre>
4+
<pre class="prettyprint lang-dart">{{{ sourceCode }}}</pre>
55
</section>{{/hasSourceCode}}

test/model_test.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:dartdoc/src/model.dart';
1212
import 'package:dartdoc/src/model_utils.dart';
1313
import 'package:dartdoc/src/package_meta.dart';
1414
import 'package:test/test.dart';
15+
import 'package:path/path.dart' as p;
1516

1617
import 'src/utils.dart' as utils;
1718

@@ -805,6 +806,7 @@ void main() {
805806
});
806807

807808
test('has source code', () {
809+
initializeConfig(addCrossdart: false);
808810
expect(topLevelFunction.sourceCode, startsWith('@deprecated'));
809811
expect(topLevelFunction.sourceCode, endsWith('''
810812
String topLevelFunction(int param1, bool param2, Cool coolBeans,
@@ -844,6 +846,13 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
844846
.singleWhere((m) => m.name == 'convertToMap');
845847
});
846848

849+
tearDown(() {
850+
var file = new File(p.join(Directory.current.path, "crossdart.json"));
851+
if (file.existsSync()) {
852+
file.deleteSync();
853+
}
854+
});
855+
847856
test('has a fully qualified name', () {
848857
expect(m1.fullyQualifiedName, 'ex.B.m1');
849858
});
@@ -920,7 +929,22 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
920929
});
921930

922931
test('method source code indents correctly', () {
923-
expect(convertToMap.sourceCode, 'Map<X, Y> convertToMap() => null;');
932+
initializeConfig(addCrossdart: false);
933+
expect(convertToMap.sourceCode,
934+
'Map&lt;X, Y&gt; convertToMap() =&gt; null;');
935+
});
936+
937+
test('method source code crossdartifies correctly', () {
938+
convertToMap.clearSourceCodeCache();
939+
new File(p.join(Directory.current.path, "crossdart.json"))
940+
.writeAsStringSync("""
941+
{"testing/test_package/lib/fake.dart":
942+
{"references":[{"offset":5806,"end":5809,"remotePath":"http://www.example.com/fake.dart"}]}}
943+
""");
944+
945+
initializeConfig(addCrossdart: true, inputDir: Directory.current);
946+
expect(convertToMap.sourceCode,
947+
"<a class='crossdart-link' href='http://www.example.com/fake.dart'>Map</a>&lt;X, Y&gt; convertToMap() =&gt; null;");
924948
});
925949

926950
group(".crossdartHtmlTag()", () {
@@ -929,7 +953,7 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
929953
String packageName = m1.library.package.name;
930954
String packageVersion = m1.library.package.version;
931955
expect(m1.crossdartHtmlTag,
932-
contains("//crossdart.info/p/$packageName/$packageVersion"));
956+
contains("//www.crossdart.info/p/$packageName/$packageVersion"));
933957
});
934958

935959
test('it returns an empty string when Crossdart support is disabled', () {

0 commit comments

Comments
 (0)