Prevent crashes/corruption from inability to detect special objects in the right order. (#1729)
* dartfmt
* Fix crash in angular, rearrange special object handling, and test angular doc builds
* dartfmt
* Pin to .69 until other changes go through
* Pin appveyor too
* Fix CI typos
* Fix problem with embeddeders that don't provide Interceptor (mark it optional)
* Fix Flutter analysis errors from forced-load Interceptor attempt
* Check for null embedder sdk
diff --git a/.travis.yml b/.travis.yml
index 794905e..d4b915b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,10 @@
language: dart
sudo: false
dart:
- - "dev/raw/latest"
+ - "dev/raw/2.0.0-dev.69.0"
env:
- DARTDOC_BOT=main
- # TODO(devoncarew): add angulardart support
- #- DARTDOC_BOT=angular
+ - DARTDOC_BOT=packages
- DARTDOC_BOT=flutter
- DARTDOC_BOT=sdk-docs
script: ./tool/travis.sh
diff --git a/analysis_options.yaml b/analysis_options.yaml
index ee60de8..83921c5 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,7 +1,5 @@
analyzer:
- strong-mode: true
language:
- enableGenericMethods: true
enablePreviewDart2: true
enableSuperMixins: true
exclude:
diff --git a/appveyor.yml b/appveyor.yml
index d4bd319..8cd0e15 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
install:
- - ps: wget https://storage.googleapis.com/dart-archive/channels/dev/raw/latest/sdk/dartsdk-windows-x64-release.zip -OutFile dart-sdk.zip
+ - ps: wget https://storage.googleapis.com/dart-archive/channels/dev/raw/2.0.0-dev.69.0/sdk/dartsdk-windows-x64-release.zip -OutFile dart-sdk.zip
- cmd: echo "Unzipping dart-sdk..."
- cmd: 7z x dart-sdk.zip -o"C:\tools" -y > nul
- set PATH=%PATH%;C:\tools\dart-sdk\bin
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 530a9d6..7ed5a5d 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -44,6 +44,7 @@
import 'package:dartdoc/src/markdown_processor.dart' show Documentation;
import 'package:dartdoc/src/model_utils.dart';
import 'package:dartdoc/src/package_meta.dart' show PackageMeta, FileContents;
+import 'package:dartdoc/src/special_elements.dart';
import 'package:dartdoc/src/utils.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:front_end/src/byte_store/byte_store.dart';
@@ -180,18 +181,17 @@
List<Class> get inheritance {
List<Class> inheritance = [];
inheritance.addAll((enclosingElement as Class).inheritanceChain);
+ Class object = packageGraph.specialClasses[SpecialClass.object];
if (!inheritance.contains(definingEnclosingElement) &&
definingEnclosingElement != null) {
- assert(definingEnclosingElement == packageGraph.objectElement);
+ assert(definingEnclosingElement == object);
}
// Unless the code explicitly extends dart-core's Object, we won't get
// an entry here. So add it.
- if (inheritance.last != packageGraph.objectElement &&
- packageGraph.objectElement != null) {
- inheritance.add(packageGraph.objectElement);
+ if (inheritance.last != object && object != null) {
+ inheritance.add(object);
}
- assert(
- inheritance.where((e) => e == packageGraph.objectElement).length == 1);
+ assert(inheritance.where((e) => e == object).length == 1);
return inheritance;
}
}
@@ -1629,7 +1629,7 @@
buffer.write('${getter.oneLineDoc}');
}
if (hasPublicSetter && setter.oneLineDoc.isNotEmpty) {
- buffer.write('${getterSetterBothAvailable ? "": setter.oneLineDoc}');
+ buffer.write('${getterSetterBothAvailable ? "" : setter.oneLineDoc}');
}
_oneLineDoc = buffer.toString();
}
@@ -2587,21 +2587,6 @@
if (e is ClassElement) {
if (!e.isEnum) {
newModelElement = new Class(e, library, packageGraph);
- if (newModelElement.name == 'Object' &&
- newModelElement.library.name == 'dart:core') {
- // We've found Object. This is an important object, so save it in the package.
- assert(
- newModelElement.library.packageGraph._objectElement == null);
- newModelElement.library.packageGraph._objectElement =
- newModelElement;
- }
- if (newModelElement.name == 'Interceptor' &&
- newModelElement.library.name == 'dart:_interceptors') {
- // We've found Interceptor. Another important object.
- assert(!newModelElement.library.packageGraph._interceptorUsed);
- newModelElement.library.packageGraph.interceptor =
- newModelElement;
- }
} else {
newModelElement = new Enum(e, library, packageGraph);
}
@@ -4017,8 +4002,19 @@
// TODO(jcollins-g): This constructor is convoluted. Clean this up by
// building Libraries and adding them to Packages, then adding Packages
// to this graph.
- PackageGraph(Iterable<LibraryElement> libraryElements, this.config,
- this.packageMeta, this._packageWarningOptions, this.driver, this.sdk) {
+
+ /// Construct a package graph.
+ /// [libraryElements] - Libraries to be documented.
+ /// [specialLibraryElements] - Any libraries that may not be documented, but
+ /// contain required [SpecialClass]es.
+ PackageGraph(
+ Iterable<LibraryElement> libraryElements,
+ Iterable<LibraryElement> specialLibraryElements,
+ this.config,
+ this.packageMeta,
+ this._packageWarningOptions,
+ this.driver,
+ this.sdk) {
assert(_allConstructedModelElements.isEmpty);
assert(allLibraries.isEmpty);
_packageWarningCounter = new PackageWarningCounter(_packageWarningOptions);
@@ -4042,11 +4038,19 @@
new Package.fromPackageMeta(packageMeta, this);
allLibrariesAdded = true;
+ // [findOrCreateLibraryFor] already adds to the proper structures.
+ specialLibraryElements.forEach((element) {
+ findOrCreateLibraryFor(element);
+ });
+
// Go through docs of every ModelElement in package to pre-build the macros
// index.
allLocalModelElements.forEach((m) => m.documentationLocal);
_macrosAdded = true;
+ // Scan all model elements to insure that interceptor and other special
+ // objects are found.
+ specialClasses = new SpecialClasses(this);
// After the allModelElements traversal to be sure that all packages
// are picked up.
documentedPackages.toList().forEach((package) {
@@ -4059,6 +4063,8 @@
allImplementorsAdded = true;
}
+ SpecialClasses specialClasses;
+
/// It is safe to cache values derived from the _implementors table if this
/// is true.
bool allImplementorsAdded = false;
@@ -4497,31 +4503,6 @@
return _localPublicLibraries;
}
- // Written from ModelElement.from.
- ModelElement _objectElement;
-
- // Return the element for "Object".
- ModelElement get objectElement {
- assert(_objectElement != null);
- return _objectElement;
- }
-
- // Don't let this be used for canonicalization before we find it.
- bool _interceptorUsed = false;
- Class _interceptor;
-
- /// Return the element for "Interceptor", a Dart implementation class intended
- /// to function the same as Object.
- Class get interceptor {
- _interceptorUsed = true;
- return _interceptor;
- }
-
- set interceptor(Class newInterceptor) {
- assert(_interceptorUsed == false);
- _interceptor = newInterceptor;
- }
-
// Return the set of [Class]es objects should inherit through if they
// show up in the inheritance chain. Do not call before interceptorElement is
// found. Add classes here if they are similar to Interceptor in that they
@@ -4531,7 +4512,7 @@
Set<Class> get inheritThrough {
if (_inheritThrough == null) {
_inheritThrough = new Set();
- _inheritThrough.add(interceptor);
+ _inheritThrough.add(specialClasses[SpecialClass.interceptor]);
}
return _inheritThrough;
}
@@ -4746,16 +4727,16 @@
return foundLibrary;
}
- List<ModelElement> _allModelElements;
+ List<ModelElement> _allLocalModelElements;
Iterable<ModelElement> get allLocalModelElements {
assert(allLibrariesAdded);
- if (_allModelElements == null) {
- _allModelElements = [];
+ if (_allLocalModelElements == null) {
+ _allLocalModelElements = [];
this.localLibraries.forEach((library) {
- _allModelElements.addAll(library.allModelElements);
+ _allLocalModelElements.addAll(library.allModelElements);
});
}
- return _allModelElements;
+ return _allLocalModelElements;
}
List<ModelElement> _allCanonicalModelElements;
@@ -5512,9 +5493,16 @@
if (packageMeta.needsPubGet) {
packageMeta.runPubGet();
}
- Set<LibraryElement> libraries = await getLibraries(getFiles);
- return new PackageGraph(libraries, config, config.topLevelPackageMeta,
- getWarningOptions(), driver, sdk);
+ Set<LibraryElement> libraries = new Set();
+ Set<LibraryElement> specialLibraries = new Set();
+ DartSdk findSpecialsSdk = sdk;
+ if (embedderSdk != null && embedderSdk.urlMappings.isNotEmpty) {
+ findSpecialsSdk = embedderSdk;
+ }
+ await getLibraries(libraries, specialLibraries, getFiles,
+ specialLibraryFiles(findSpecialsSdk).toSet());
+ return new PackageGraph(libraries, specialLibraries, config,
+ config.topLevelPackageMeta, getWarningOptions(), driver, sdk);
}
DartSdk _sdk;
@@ -5711,7 +5699,8 @@
return metas;
}
- Future<List<LibraryElement>> _parseLibraries(Set<String> files) async {
+ Future<List<LibraryElement>> _parseLibraries(Set<String> files,
+ {bool throwErrors = true}) async {
Set<LibraryElement> libraries = new Set();
Set<Source> originalSources;
Set<Source> sources = new Set<Source>();
@@ -5724,8 +5713,8 @@
driver.addFile(filename);
addedFiles.add(filename);
});
- await Future
- .wait(files.map((f) => processLibrary(f, libraries, sources)));
+ await Future.wait(
+ files.map((f) => processLibrary(f, libraries, sources)));
/// We don't care about upstream analysis errors, so save the first
/// source list.
@@ -5747,7 +5736,7 @@
}
} while (!lastPass.containsAll(current));
- await logAnalysisErrors(originalSources);
+ if (throwErrors) await logAnalysisErrors(originalSources);
return libraries.toList();
}
@@ -5824,9 +5813,18 @@
return new Set.from(files.map((s) => new File(s).absolute.path));
}
- Future<Set<LibraryElement>> getLibraries(Set<String> files) async {
- Set<LibraryElement> libraries = new Set();
+ Future<void> getLibraries(
+ Set<LibraryElement> libraries,
+ Set<LibraryElement> specialLibraries,
+ Set<String> files,
+ Set<String> specialFiles) async {
libraries.addAll(await _parseLibraries(files));
+
+ /// Flutter doesn't seem to like being given the Interceptor library.
+ /// But it doesn't need it, either. So just skip reporting errors here.
+ specialLibraries.addAll(await _parseLibraries(
+ specialFiles.difference(files),
+ throwErrors: false));
if (config.include.isNotEmpty) {
Iterable knownLibraryNames = libraries.map((l) => l.name);
Set notFound = new Set.from(config.include)
@@ -5838,7 +5836,6 @@
}
libraries.removeWhere((lib) => !config.include.contains(lib.name));
}
- return libraries;
}
/// If [dir] contains both a `lib` directory and a `pubspec.yaml` file treat
diff --git a/lib/src/special_elements.dart b/lib/src/special_elements.dart
new file mode 100644
index 0000000..ec72722
--- /dev/null
+++ b/lib/src/special_elements.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2018, 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.
+
+/// Handling for special elements within Dart. When identified these
+/// may alter the interpretation and documentation generated for other
+/// [ModelElement]s.
+///
+/// Finding these must not depend on canonicalization.
+library dartdoc.special_elements;
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:dartdoc/src/model.dart';
+
+/// Which of the [SpecialClasses] to get.
+enum SpecialClass {
+ /// From dart:core, Object
+ object,
+
+ /// From dart:_interceptors, Interceptor
+ interceptor,
+}
+
+/// A declaration of a special [Class] and how to find it.
+class _SpecialClassDefinition {
+ /// Which specialElement this is.
+ final SpecialClass specialClass;
+
+ /// Name of the ModelElement.
+ final String name;
+
+ /// The library name for the [LibraryElement] in which this [ModelElement]
+ /// can be found.
+ final String libraryName;
+
+ /// The package name in which this [ModelElement] can be found.
+ final String packageName;
+
+ /// The URI for the library in which this [ModelElement] is defined.
+ final String specialFileUri;
+
+ /// If true, require this element to exist in the packageGraph when
+ /// calling the [SpecialClasses] constructor.
+ final bool required;
+ _SpecialClassDefinition(
+ this.specialClass, this.name, this.libraryName, this.specialFileUri,
+ {this.required = true, this.packageName = 'Dart'});
+
+ /// Get the filename for the Dart Library where this [specialClass] is
+ /// declared.
+ String getSpecialFilename(DartSdk sdk) =>
+ sdk.mapDartUri(specialFileUri)?.fullName;
+
+ bool matchesClass(Class modelClass) {
+ return modelClass.name == name &&
+ modelClass.library.element.name == libraryName &&
+ modelClass.package.name == packageName;
+ }
+}
+
+/// List all special classes we need to find here.
+final List<_SpecialClassDefinition> _specialClassDefinitions = [
+ new _SpecialClassDefinition(
+ SpecialClass.object, 'Object', 'dart.core', 'dart:core'),
+ new _SpecialClassDefinition(SpecialClass.interceptor, 'Interceptor',
+ '_interceptors', 'dart:_interceptors',
+ required: false),
+];
+
+/// Given a SDK, resolve URIs for the libraries containing our special
+/// clases.
+Set<String> specialLibraryFiles(DartSdk sdk) => _specialClassDefinitions
+ .map((_SpecialClassDefinition d) => d.getSpecialFilename(sdk))
+ .where((String s) => s != null)
+ .toSet();
+
+Set<String> __specialLibraryNames;
+
+/// These library names can be checked against the [LibraryElement] names
+/// to avoid traversing libraries we don't need to.
+Set<String> get _specialLibraryNames {
+ if (__specialLibraryNames == null) {
+ __specialLibraryNames = _specialClassDefinitions
+ .map((_SpecialClassDefinition d) => d.libraryName)
+ .toSet();
+ }
+ return __specialLibraryNames;
+}
+
+/// Class for managing special [Class] objects inside Dartdoc.
+class SpecialClasses {
+ final PackageGraph packageGraph;
+ final Map<SpecialClass, Class> _specialClass = {};
+
+ SpecialClasses(this.packageGraph) {
+ Set<LibraryElement> doneKeys = new Set();
+ Set<LibraryElement> keysToDo = new Set.from(packageGraph.allLibraries.keys);
+ // Loops because traversing the libraries can instantiate additional
+ // libraries, and does so in this manner to avoid running into iterable
+ // modification exceptions.
+ while (keysToDo.isNotEmpty) {
+ keysToDo.forEach((LibraryElement e) {
+ if (_specialLibraryNames.contains(e.name)) {
+ packageGraph.allLibraries[e].allClasses.forEach((Class aClass) {
+ _specialClassDefinitions.forEach((_SpecialClassDefinition d) {
+ if (d.matchesClass(aClass)) {
+ assert(!_specialClass.containsKey(d.specialClass) ||
+ _specialClass[d.specialClass] == aClass);
+ _specialClass[d.specialClass] = aClass;
+ }
+ });
+ });
+ }
+ doneKeys.add(e);
+ });
+ keysToDo = new Set.from(packageGraph.allLibraries.keys
+ .where((LibraryElement e) => !doneKeys.contains(e)));
+ }
+ _specialClassDefinitions.forEach((_SpecialClassDefinition d) {
+ if (d.required) assert(_specialClass.containsKey(d.specialClass));
+ });
+ }
+
+ Class operator [](SpecialClass specialClass) => _specialClass[specialClass];
+}
diff --git a/pubspec.lock b/pubspec.lock
index d00fc3b..990285f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -492,4 +492,4 @@
source: hosted
version: "2.1.13"
sdks:
- dart: ">=2.0.0-dev.59.0 <=2.0.0-dev.63.0.flutter-4c9689c1d2"
+ dart: ">=2.0.0-dev.59.0 <=2.0.0-dev.69.0"
diff --git a/test/compare_output_test.dart b/test/compare_output_test.dart
index 3fcb718..b29a583 100644
--- a/test/compare_output_test.dart
+++ b/test/compare_output_test.dart
@@ -200,8 +200,7 @@
fail('dartdoc failed');
}
- var jsonValues = LineSplitter
- .split(result.stdout)
+ var jsonValues = LineSplitter.split(result.stdout)
.map((j) => json.decode(j) as Map<String, dynamic>)
.toList();
diff --git a/test/dartdoc_options_test.dart b/test/dartdoc_options_test.dart
index a3b4fdd..917cbea 100644
--- a/test/dartdoc_options_test.dart
+++ b/test/dartdoc_options_test.dart
@@ -498,7 +498,14 @@
errorMessage,
equals(
'Field dartdoc.fileOptionList from ${pathLib.canonicalize(dartdocOptionsTwo.path)}, set to [existing.dart, thing/that/does/not/exist], resolves to missing path: '
- '"${pathLib.joinAll([pathLib.canonicalize(secondDir.path), 'thing', 'that', 'does', 'not', 'exist'])}"'));
+ '"${pathLib.joinAll([
+ pathLib.canonicalize(secondDir.path),
+ 'thing',
+ 'that',
+ 'does',
+ 'not',
+ 'exist'
+ ])}"'));
// It doesn't matter that this fails.
expect(
dartdocOptionSetFiles['nonCriticalFileOption'].valueAt(firstDir),
@@ -519,7 +526,10 @@
errorMessage,
equals(
'Field dartdoc.fileOption from ${pathLib.canonicalize(dartdocOptionsTwo.path)}, set to not existing, resolves to missing path: '
- '"${pathLib.joinAll([pathLib.canonicalize(secondDir.path), "not existing"])}"'));
+ '"${pathLib.joinAll([
+ pathLib.canonicalize(secondDir.path),
+ "not existing"
+ ])}"'));
});
test('DartdocOptionSetFile works for directory options', () {
diff --git a/test/model_test.dart b/test/model_test.dart
index bd3077e..ca93b77 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -8,6 +8,7 @@
import 'package:dartdoc/dartdoc.dart';
import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/special_elements.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as pathLib;
import 'package:test/test.dart';
@@ -305,7 +306,8 @@
htmlLibrary.allClasses.singleWhere((c) => c.name == 'EventTarget');
Field hashCode = EventTarget.allPublicInstanceProperties
.singleWhere((f) => f.name == 'hashCode');
- Class objectModelElement = sdkAsPackageGraph.objectElement;
+ Class objectModelElement =
+ sdkAsPackageGraph.specialClasses[SpecialClass.object];
// If this fails, EventTarget might have been changed to no longer
// inherit from Interceptor. If that's true, adjust test case to
// another class that does.
@@ -1693,7 +1695,7 @@
new File(pathLib.join(utils.testPackageDir.path, "crossdart.json"));
crossDartFile.writeAsStringSync("""
{"$fakePath":
- {"references":[{"offset":${offset},"end":${offset+3},"remotePath":"http://www.example.com/fake.dart"}]}}
+ {"references":[{"offset":${offset},"end":${offset + 3},"remotePath":"http://www.example.com/fake.dart"}]}}
""");
// Indirectly load the file.
crossdartPackageGraph.crossdartJson;
diff --git a/test/src/utils.dart b/test/src/utils.dart
index a2dc71d..688677c 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -44,8 +44,8 @@
/// Convenience factory to build a [DartdocOptionContext] and associate it with a
/// [DartdocOptionSet] based on the current working directory.
Future<DartdocOptionContext> contextFromArgv(List<String> argv) async {
- DartdocOptionSet optionSet = await DartdocOptionSet
- .fromOptionGenerators('dartdoc', [createDartdocOptions]);
+ DartdocOptionSet optionSet = await DartdocOptionSet.fromOptionGenerators(
+ 'dartdoc', [createDartdocOptions]);
optionSet.parseArguments(argv);
return new DartdocOptionContext(optionSet, Directory.current);
}
diff --git a/tool/doc_packages.dart b/tool/doc_packages.dart
index 0c31e56..ade349f 100644
--- a/tool/doc_packages.dart
+++ b/tool/doc_packages.dart
@@ -196,8 +196,7 @@
{String cwd,
bool quiet: false,
Duration timeout: const Duration(seconds: 60)}) {
- return Process
- .start(command, args, workingDirectory: cwd)
+ return Process.start(command, args, workingDirectory: cwd)
.then((Process process) {
if (!quiet) {
process.stdout.listen((bytes) => _log(utf8.decode(bytes)));
diff --git a/tool/grind.dart b/tool/grind.dart
index 3cb6104..99d3b1a 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -586,7 +586,8 @@
}
/// Returns the directory in which we generated documentation.
-Future<String> _buildPubPackageDocs(String pubPackageName,
+Future<String> _buildPubPackageDocs(
+ String pubPackageName, List<String> dartdocParameters,
[String version, String label]) async {
Map<String, String> env = _createThrowawayPubCache();
var launcher = new SubprocessLauncher(
@@ -609,19 +610,24 @@
pathLib.join(Directory.current.absolute.path, 'bin', 'dartdoc.dart'),
'--json',
'--show-progress',
- ]..addAll(extraDartdocParameters),
+ ]..addAll(dartdocParameters),
workingDirectory: pubPackageDir.absolute.path);
return pathLib.join(pubPackageDir.absolute.path, 'doc', 'api');
}
@Task(
- 'Serve an arbitrary pub package based on PACKAGE_NAME and PACKAGE_VERSION environment variables')
-servePubPackage() async {
+ 'Build an arbitrary pub package based on PACKAGE_NAME and PACKAGE_VERSION environment variables')
+Future<String> buildPubPackage() async {
assert(Platform.environment.containsKey('PACKAGE_NAME'));
String packageName = Platform.environment['PACKAGE_NAME'];
String version = Platform.environment['PACKAGE_VERSION'];
- _serveDocsFrom(await _buildPubPackageDocs(packageName, version), 9000,
- 'serve-pub-package');
+ return _buildPubPackageDocs(packageName, extraDartdocParameters, version);
+}
+
+@Task(
+ 'Serve an arbitrary pub package based on PACKAGE_NAME and PACKAGE_VERSION environment variables')
+servePubPackage() async {
+ _serveDocsFrom(await buildPubPackage(), 9000, 'serve-pub-package');
}
@Task('Checks that CHANGELOG mentions current version')
diff --git a/tool/travis.sh b/tool/travis.sh
index 6f4cceb..1445fb0 100755
--- a/tool/travis.sh
+++ b/tool/travis.sh
@@ -20,6 +20,9 @@
elif [ "$DARTDOC_BOT" = "flutter" ]; then
echo "Running flutter dartdoc bot"
pub run grinder validate-flutter-docs
+elif [ "$DARTDOC_BOT" = "packages" ]; then
+ echo "Running packages dartdoc bot"
+ PACKAGE_NAME=angular PACKAGE_VERSION=">=5.0.0-beta" DARTDOC_PARAMS="--include=angular,angular.security" pub run grinder build-pub-package
else
echo "Running main dartdoc bot"
pub run grinder buildbot