Skip to content
This repository was archived by the owner on Jun 20, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 116 additions & 51 deletions analyzer_plugin/lib/src/angular_driver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ class AngularDriver
final _addedFiles = new LinkedHashSet<String>();
final _dartFiles = new LinkedHashSet<String>();
final _changedFiles = new LinkedHashSet<String>();
final _requestedFiles = new HashSet<String>();
final _requestedDartFiles = new Map<String, List<Completer>>();
final _requestedHtmlFiles = new Map<String, List<Completer>>();
final _filesToAnalyze = new HashSet<String>();
final _htmlViewsToAnalyze = new HashSet<Tuple2<String, String>>();
final _htmlFilesToAnalyze = new HashSet<String>();
final ByteStore byteStore;
FileTracker _fileTracker;
final lastSignatures = <String, String>{};
Expand All @@ -66,7 +67,10 @@ class AngularDriver
}

bool get hasFilesToAnalyze =>
_filesToAnalyze.isNotEmpty || _htmlViewsToAnalyze.isNotEmpty;
_filesToAnalyze.isNotEmpty ||
_htmlFilesToAnalyze.isNotEmpty ||
_requestedDartFiles.isNotEmpty ||
_requestedHtmlFiles.isNotEmpty;

bool _ownsFile(String path) {
return path.endsWith('.dart') || path.endsWith('.html');
Expand All @@ -85,15 +89,9 @@ class AngularDriver
void fileChanged(String path) {
if (_ownsFile(path)) {
if (path.endsWith('.html')) {
for (final dartContext
in _fileTracker.getDartPathsReferencingHtml(path)) {
_htmlViewsToAnalyze.add(new Tuple2(path, dartContext));
}
_htmlFilesToAnalyze.add(path);
for (final path in _fileTracker.getHtmlPathsReferencingHtml(path)) {
for (final dartContext
in _fileTracker.getDartPathsReferencingHtml(path)) {
_htmlViewsToAnalyze.add(new Tuple2(path, dartContext));
}
_htmlFilesToAnalyze.add(path);
}
for (final path in _fileTracker.getDartPathsAffectedByHtml(path)) {
_filesToAnalyze.add(path);
Expand All @@ -105,19 +103,38 @@ class AngularDriver
_scheduler.notify(this);
}

Future<List<AnalysisError>> requestDartErrors(String path) {
var completer = new Completer<List<AnalysisError>>();
_requestedDartFiles
.putIfAbsent(path, () => <Completer<List<AnalysisError>>>[])
.add(completer);
_scheduler.notify(this);
return completer.future;
}

Future<List<AnalysisError>> requestHtmlErrors(String path) {
var completer = new Completer<List<AnalysisError>>();
_requestedHtmlFiles
.putIfAbsent(path, () => <Completer<List<AnalysisError>>>[])
.add(completer);
_scheduler.notify(this);
return completer.future;
}

AnalysisDriverPriority get workPriority {
if (standardHtml == null) {
return AnalysisDriverPriority.interactive;
}

if (_requestedFiles.isNotEmpty) {
if (_requestedDartFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_requestedHtmlFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
// tasks here?
if (_filesToAnalyze.isNotEmpty) {
return AnalysisDriverPriority.general;
}
if (_htmlViewsToAnalyze.isNotEmpty) {
if (_htmlFilesToAnalyze.isNotEmpty) {
return AnalysisDriverPriority.general;
}
if (_changedFiles.isNotEmpty) {
Expand All @@ -138,16 +155,49 @@ class AngularDriver
return;
}

if (_requestedFiles.isNotEmpty) {
final path = _requestedFiles.first;
try {
pushDartErrors(path);
pushDartNavigation(path);
pushDartOccurrences(path);
_requestedFiles.remove(path);
} catch (e) {
e;
if (_requestedDartFiles.isNotEmpty) {
final path = _requestedDartFiles.keys.first;
final completers = _requestedDartFiles.remove(path);
// Note: We can't use await here, or the dart analysis becomes a future in
// a queue that won't be completed until the scheduler schedules the dart
// driver, which doesn't happen because its waiting for us.
resolveDart(path, onlyIfChangedSignature: false).then((result) {
completers
.forEach((completer) => completer.complete(result?.errors ?? []));
}, onError: (e) {
completers.forEach((completer) => completer.completeError(e));
});

return;
}

if (_requestedHtmlFiles.isNotEmpty) {
final path = _requestedHtmlFiles.keys.first;
final completers = _requestedHtmlFiles.remove(path);
// Note: We can't use await here, or the dart analysis becomes a future in
// a queue that won't be completed until the scheduler schedules the dart
// driver, which doesn't happen because its waiting for us.
// ALSO assume .dart and .html paths correlate, otherwise we'd have to
// wait for all dart analysis to complete.
Future resolvedHtml;

// Try resolving HTML using the existing dart/html relationships which may
// be already known. However, if we don't see any relationships, try using
// the .dart equivalent. Better than no result -- the real one WILL come.
if (_fileTracker.getDartPathsReferencingHtml(path).isEmpty) {
resolvedHtml = resolveHtmlFrom(path, path.replaceAll(".html", ".dart"));
} else {
resolvedHtml = resolveHtml(path);
}

// After whichever resolution is complete, push errors.
resolvedHtml.then((result) {
completers
.forEach((completer) => completer.complete(result?.errors ?? []));
}, onError: (e) {
completers.forEach((completer) => completer.completeError(e));
});

return;
}

Expand All @@ -158,10 +208,10 @@ class AngularDriver
return;
}

if (_htmlViewsToAnalyze.isNotEmpty) {
final info = _htmlViewsToAnalyze.first;
pushHtmlErrors(info.item1, info.item2);
_htmlViewsToAnalyze.remove(info);
if (_htmlFilesToAnalyze.isNotEmpty) {
final path = _htmlFilesToAnalyze.first;
pushHtmlErrors(path);
_htmlFilesToAnalyze.remove(path);
return;
}

Expand Down Expand Up @@ -221,8 +271,8 @@ class AngularDriver
errorCode, error.message, error.correction);
}

String getHtmlKey(String htmlPath, String dartPath) {
final key = _fileTracker.getHtmlSignature(htmlPath, dartPath);
String getHtmlKey(String htmlPath) {
final key = _fileTracker.getHtmlSignature(htmlPath);
return key.toHex() + '.ngresolved';
}

Expand All @@ -239,8 +289,8 @@ class AngularDriver
source.exists() ? source.contents.data : "")(getSource(path));
}

Future<DirectivesResult> resolveHtml(String htmlPath, String dartPath) async {
final key = getHtmlKey(htmlPath, dartPath);
Future<DirectivesResult> resolveHtml(String htmlPath) async {
final key = getHtmlKey(htmlPath);
final htmlSource = _sourceFactory.forUri("file:" + htmlPath);
final List<int> bytes = byteStore.get(key);
if (bytes != null) {
Expand All @@ -251,9 +301,36 @@ class AngularDriver
return new DirectivesResult([], errors);
}

final result = new DirectivesResult([], []);

for (final dartContext
in _fileTracker.getDartPathsReferencingHtml(htmlPath)) {
final pairResult = await resolveHtmlFrom(htmlPath, dartContext);
result.directives.addAll(pairResult.directives);
result.errors.addAll(pairResult.errors);
}

final summary = new LinkedHtmlSummaryBuilder()
..errors = summarizeErrors(
result.errors.where((error) => error is! FromFilePrefixedError))
..errorsFromPath = result.errors
.where((error) => error is FromFilePrefixedError)
.map((error) => new SummarizedAnalysisErrorFromPathBuilder()
..path = (error as FromFilePrefixedError).fromSourcePath
..originalError =
summarizeError((error as FromFilePrefixedError).originalError));
final List<int> newBytes = summary.toBuffer();
byteStore.put(key, newBytes);

return result;
}

Future<DirectivesResult> resolveHtmlFrom(
String htmlPath, String dartPath) async {
final result = await getDirectives(dartPath);
final directives = result.directives;
final unit = (await dartDriver.getUnitElement(dartPath)).element;
final htmlSource = _sourceFactory.forUri("file:" + htmlPath);

if (unit == null) return null;
final context = unit.context;
Expand Down Expand Up @@ -318,17 +395,6 @@ class AngularDriver
}
}

final summary = new LinkedHtmlSummaryBuilder()
..errors = summarizeErrors(
errors.where((error) => error is! FromFilePrefixedError))
..errorsFromPath = errors
.where((error) => error is FromFilePrefixedError)
.map((error) => new SummarizedAnalysisErrorFromPathBuilder()
..path = (error as FromFilePrefixedError).fromSourcePath
..originalError =
summarizeError((error as FromFilePrefixedError).originalError));
final List<int> newBytes = summary.toBuffer();
byteStore.put(key, newBytes);
return new DirectivesResult(directives, errors);
}

Expand Down Expand Up @@ -364,8 +430,8 @@ class AngularDriver
return contents;
}

Future pushHtmlErrors(String htmlPath, String dartPath) async {
final errors = (await resolveHtml(htmlPath, dartPath)).errors;
Future pushHtmlErrors(String htmlPath) async {
final errors = (await resolveHtml(htmlPath)).errors;
final lineInfo = new LineInfo.fromContent(getFileContent(htmlPath));
final serverErrors = protocol.doAnalysisError_listFromEngine(
dartDriver.analysisOptions, lineInfo, errors);
Expand All @@ -389,7 +455,7 @@ class AngularDriver
}

Future<DirectivesResult> resolveDart(String path,
{bool withDirectives: false}) async {
{bool withDirectives: false, bool onlyIfChangedSignature: true}) async {
final baseKey = await dartDriver.getUnitElementSignature(path);

// This happens when the path is..."hidden by a generated file"..whch I
Expand All @@ -402,7 +468,7 @@ class AngularDriver

final key = baseKey + '.ngresolved';

if (lastSignatures[path] == key) {
if (lastSignatures[path] == key && onlyIfChangedSignature) {
return null;
}

Expand All @@ -413,8 +479,8 @@ class AngularDriver
if (bytes != null) {
final summary = new LinkedDartSummary.fromBuffer(bytes);

for (final htmlView in summary.referencedHtmlFiles) {
_htmlViewsToAnalyze.add(new Tuple2(htmlView, path));
for (final htmlPath in summary.referencedHtmlFiles) {
_htmlFilesToAnalyze.add(htmlPath);
}

_fileTracker.setDartHasTemplate(path, summary.hasDartTemplates);
Expand Down Expand Up @@ -480,8 +546,7 @@ class AngularDriver
errors.addAll(tplErrorListener.errors.where(
(e) => !view.template.ignoredErrors.contains(e.errorCode.name)));
} else if (view?.templateUriSource != null) {
_htmlViewsToAnalyze
.add(new Tuple2(view.templateUriSource.fullName, path));
_htmlFilesToAnalyze.add(view.templateUriSource.fullName);
htmlViews.add(view.templateUriSource.fullName);
}

Expand Down
11 changes: 7 additions & 4 deletions analyzer_plugin/lib/src/file_tracker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,15 @@ class FileTracker {
return signature;
}

ApiSignature getHtmlSignature(String htmlPath, String dartPath) {
ApiSignature getHtmlSignature(String htmlPath) {
final signature = new ApiSignature();
signature.addBytes(_fileHasher.getContentHash(htmlPath).toByteList());
signature.addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
for (final subHtmlPath in getHtmlPathsAffectingDartContext(dartPath)) {
signature.addBytes(_fileHasher.getContentHash(subHtmlPath).toByteList());
for (final dartPath in getDartPathsReferencingHtml(htmlPath)) {
signature.addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
for (final subHtmlPath in getHtmlPathsAffectingDartContext(dartPath)) {
signature
.addBytes(_fileHasher.getContentHash(subHtmlPath).toByteList());
}
}
return signature;
}
Expand Down
69 changes: 69 additions & 0 deletions analyzer_plugin/lib/starter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:angular_analyzer_plugin/src/angular_driver.dart';
import 'package:analyzer/src/context/builder.dart';

class Starter {
final angularDrivers = <String, AngularDriver>{};
AnalysisServer server;

void start(AnalysisServer server) {
this.server = server;
ContextBuilder.onCreateAnalysisDriver = onCreateAnalysisDriver;
server.onResultErrorSupplementor = sumErrors;
server.onNoAnalysisResult = sendHtmlResult;
}

void onCreateAnalysisDriver(
analysisDriver,
scheduler,
logger,
resourceProvider,
byteStore,
contentOverlay,
driverPath,
sourceFactory,
analysisOptions) {
final AngularDriver driver = new AngularDriver(server, analysisDriver,
scheduler, byteStore, sourceFactory, contentOverlay);
angularDrivers[driverPath] = driver;
server.onFileAdded.listen((String path) {
if (server.contextManager.getContextFolderFor(path).path == driverPath) {
// only the owning driver "adds" the path
driver.addFile(path);
} else {
// but the addition of a file is a "change" to all the other drivers
driver.fileChanged(path);
}
});
server.onFileChanged.listen((String path) {
// all drivers get change notification
driver.fileChanged(path);
});
}

Future sumErrors(String path, List<AnalysisError> errors) async {
for (final driver in angularDrivers.values) {
final angularErrors = await driver.requestDartErrors(path);
errors.addAll(angularErrors);
}
return null;
}

Future sendHtmlResult(String path, Function sendFn) async {
for (final driverPath in angularDrivers.keys) {
if (server.contextManager.getContextFolderFor(path).path == driverPath) {
final driver = angularDrivers[driverPath];
// only the owning driver "adds" the path
final angularErrors = await driver.requestHtmlErrors(path);
sendFn(
driver.dartDriver.analysisOptions,
new LineInfo.fromContent(driver.getFileContent(path)),
angularErrors);
return;
}
}

send(null, null, null);
}
}
Loading