Skip to content

Commit 1430d4f

Browse files
committed
Merge pull request #1165 from astashov/add-crossdart
Add ability to "Crossdartify" the source code
2 parents cf2fe2e + 52c6739 commit 1430d4f

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)