Merge remote-tracking branch 'origin/master' into salt-hashes
diff --git a/angular_analyzer_plugin/lib/src/angular_driver.dart b/angular_analyzer_plugin/lib/src/angular_driver.dart
index e31714b..cd04e82 100644
--- a/angular_analyzer_plugin/lib/src/angular_driver.dart
+++ b/angular_analyzer_plugin/lib/src/angular_driver.dart
@@ -68,10 +68,6 @@
   }
 
   @override
-  ApiSignature getUnitElementHash(String path) =>
-      dartDriver.getUnitKeyByPath(path);
-
-  @override
   bool get hasFilesToAnalyze =>
       _filesToAnalyze.isNotEmpty ||
       _htmlFilesToAnalyze.isNotEmpty ||
@@ -110,9 +106,9 @@
 
   void fileChanged(String path) {
     if (_ownsFile(path)) {
-      if (path.endsWith('.html')) {
-        _fileTracker.rehashHtmlContents(path);
+      _fileTracker.rehashContents(path);
 
+      if (path.endsWith('.html')) {
         _htmlFilesToAnalyze.add(path);
         for (final path in _fileTracker.getHtmlPathsReferencingHtml(path)) {
           _htmlFilesToAnalyze.add(path);
@@ -329,6 +325,10 @@
         errorCode, error.message, error.correction);
   }
 
+  @override
+  ApiSignature getUnitElementHash(String path) =>
+      dartDriver.getUnitKeyByPath(path);
+
   String getHtmlKey(String htmlPath) {
     final key = _fileTracker.getHtmlSignature(htmlPath);
     return '${key.toHex()}.ngresolved';
@@ -352,8 +352,8 @@
     bool ignoreCache: false,
   }) async {
     final key = getHtmlKey(htmlPath);
-    final htmlSource = _sourceFactory.forUri('file:$htmlPath');
     final bytes = byteStore.get(key);
+    final htmlSource = _sourceFactory.forUri('file:$htmlPath');
     if (!ignoreCache && bytes != null) {
       final summary = new LinkedHtmlSummary.fromBuffer(bytes);
       final errors = new List<AnalysisError>.from(
@@ -501,7 +501,8 @@
 
   @override
   Future<List<NgContent>> getHtmlNgContent(String path) async {
-    final key = '${getContentHash(path).toHex()}.ngunlinked';
+    final baseKey = _fileTracker.getContentSignature(path).toHex();
+    final key = '$baseKey.ngunlinked';
     final bytes = byteStore.get(key);
     final source = getSource(path);
     if (bytes != null) {
@@ -553,16 +554,15 @@
 
   Future<DirectivesResult> resolveDart(String path,
       {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
     // don't understand, but, can protect against. Should not be analyzed.
     // TODO detect this on file add rather than on file analyze.
-    if (baseKey == null) {
+    if (await dartDriver.getUnitElementSignature(path) == null) {
       _dartFiles.remove(path);
       return null;
     }
 
+    final baseKey = _fileTracker.getUnitElementSignature(path).toHex();
     final key = '$baseKey.ngresolved';
 
     if (lastSignatures[path] == key && onlyIfChangedSignature) {
@@ -704,7 +704,8 @@
       (await getAngularAnnotatedClasses(path)).angularAnnotatedClasses;
 
   Future<DirectivesResult> getAngularAnnotatedClasses(String path) async {
-    final key = '${getContentHash(path).toHex()}.ngunlinked';
+    final baseKey = _fileTracker.getContentSignature(path).toHex();
+    final key = '$baseKey.ngunlinked';
     final bytes = byteStore.get(key);
     if (bytes != null) {
       final summary = new UnlinkedDartSummary.fromBuffer(bytes);
diff --git a/angular_analyzer_plugin/lib/src/file_tracker.dart b/angular_analyzer_plugin/lib/src/file_tracker.dart
index d14df22..0f766b3 100644
--- a/angular_analyzer_plugin/lib/src/file_tracker.dart
+++ b/angular_analyzer_plugin/lib/src/file_tracker.dart
@@ -8,6 +8,8 @@
 }
 
 class FileTracker {
+  static const int salt = 1;
+
   final FileHasher _fileHasher;
 
   FileTracker(this._fileHasher);
@@ -17,17 +19,23 @@
 
   final _dartFilesWithDartTemplates = new HashSet<String>();
 
-  final htmlContentHashes = <String, List<int>>{};
+  final contentHashes = <String, _FileHash>{};
 
-  void rehashHtmlContents(String path) {
-    htmlContentHashes[path] = _fileHasher.getContentHash(path).toByteList();
+  void rehashContents(String path) {
+    final signature = _fileHasher.getContentHash(path);
+    final bytes = signature.toByteList();
+    contentHashes[path] = new _FileHash(
+        bytes,
+        new ApiSignature()
+          ..addInt(salt)
+          ..addBytes(bytes));
   }
 
-  List<int> getHtmlContentHash(String path) {
-    if (htmlContentHashes[path] == null) {
-      rehashHtmlContents(path);
+  List<int> _getContentHash(String path) {
+    if (contentHashes[path] == null) {
+      rehashContents(path);
     }
-    return htmlContentHashes[path];
+    return contentHashes[path].unsaltedBytes;
   }
 
   void setDartHtmlTemplates(String dartPath, List<String> htmlPaths) =>
@@ -82,24 +90,44 @@
 
   ApiSignature getDartSignature(String dartPath) {
     final signature = new ApiSignature()
+      ..addInt(salt)
       ..addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
     for (final htmlPath in getHtmlPathsAffectingDart(dartPath)) {
-      signature.addBytes(getHtmlContentHash(htmlPath));
+      signature.addBytes(_getContentHash(htmlPath));
     }
     return signature;
   }
 
   ApiSignature getHtmlSignature(String htmlPath) {
     final signature = new ApiSignature()
-      ..addBytes(getHtmlContentHash(htmlPath));
+      ..addInt(salt)
+      ..addBytes(_getContentHash(htmlPath));
     for (final dartPath in getDartPathsReferencingHtml(htmlPath)) {
       signature.addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
       for (final subHtmlPath in getHtmlPathsAffectingDartContext(dartPath)) {
-        signature.addBytes(getHtmlContentHash(subHtmlPath));
+        signature.addBytes(_getContentHash(subHtmlPath));
       }
     }
     return signature;
   }
+
+  ApiSignature getContentSignature(String path) {
+    if (contentHashes[path] == null) {
+      rehashContents(path);
+    }
+    return contentHashes[path].saltedSignature;
+  }
+
+  ApiSignature getUnitElementSignature(String path) => new ApiSignature()
+    ..addInt(salt)
+    ..addBytes(_fileHasher.getUnitElementHash(path).toByteList());
+}
+
+class _FileHash {
+  final List<int> unsaltedBytes;
+  final ApiSignature saltedSignature;
+
+  _FileHash(this.unsaltedBytes, this.saltedSignature);
 }
 
 class _RelationshipTracker {
diff --git a/angular_analyzer_plugin/test/file_tracker_test.dart b/angular_analyzer_plugin/test/file_tracker_test.dart
index a5e4868..fb84994 100644
--- a/angular_analyzer_plugin/test/file_tracker_test.dart
+++ b/angular_analyzer_plugin/test/file_tracker_test.dart
@@ -286,6 +286,7 @@
         .thenReturn(fooDartElementSignature);
 
     final expectedSignature = new ApiSignature()
+      ..addInt(FileTracker.salt)
       ..addBytes(fooDartElementSignature.toByteList())
       ..addBytes(barHtmlSignature.toByteList());
 
@@ -314,6 +315,7 @@
         .thenReturn(fooTestDartElementSignature);
 
     final expectedSignature = new ApiSignature()
+      ..addInt(FileTracker.salt)
       ..addBytes(fooHtmlSignature.toByteList())
       ..addBytes(fooDartElementSignature.toByteList())
       ..addBytes(barHtmlSignature.toByteList())
@@ -329,15 +331,40 @@
     when(_fileHasher.getContentHash("foo.html")).thenReturn(fooHtmlSignature);
 
     for (var i = 0; i < 3; ++i) {
-      _fileTracker.getHtmlContentHash("foo.html");
+      _fileTracker.getContentSignature("foo.html");
       verify(_fileHasher.getContentHash("foo.html")).once();
     }
 
+    _fileTracker.rehashContents("foo.html");
+
     for (var i = 0; i < 3; ++i) {
-      _fileTracker.rehashHtmlContents("foo.html");
+      _fileTracker.getContentSignature("foo.html");
       verify(_fileHasher.getContentHash("foo.html")).times(2);
     }
   }
+
+  // ignore: non_constant_identifier_names
+  void test_getContentHashIsSalted() {
+    final fooHtmlSignature = new ApiSignature()..addInt(1);
+    final expectedSignature = new ApiSignature()
+      ..addInt(FileTracker.salt)
+      ..addBytes(fooHtmlSignature.toByteList());
+    when(_fileHasher.getContentHash("foo.html")).thenReturn(fooHtmlSignature);
+    expect(_fileTracker.getContentSignature("foo.html").toHex(),
+        equals(expectedSignature.toHex()));
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_getUnitElementSignatureIsSalted() {
+    final fooDartElementSignature = new ApiSignature()..addInt(1);
+    final expectedSignature = new ApiSignature()
+      ..addInt(FileTracker.salt)
+      ..addBytes(fooDartElementSignature.toByteList());
+    when(_fileHasher.getUnitElementHash("foo.dart"))
+        .thenReturn(fooDartElementSignature);
+    expect(_fileTracker.getUnitElementSignature("foo.dart").toHex(),
+        equals(expectedSignature.toHex()));
+  }
 }
 
 class _FileHasherMock extends TypedMock implements FileHasher {}
diff --git a/angular_analyzer_plugin/test/test_all.dart b/angular_analyzer_plugin/test/test_all.dart
index 333fda0..6d1a1b8 100644
--- a/angular_analyzer_plugin/test/test_all.dart
+++ b/angular_analyzer_plugin/test/test_all.dart
@@ -2,6 +2,7 @@
 
 import 'angular_driver_test.dart' as angular_driver_test;
 import 'completion_contributor_test.dart' as completion_contributor_test;
+import 'file_tracker_test.dart' as file_tracker_test;
 import 'navigation_test.dart' as navigation_test;
 import 'offsetting_constant_value_visitor_test.dart'
     as offsetting_constant_value_visitor_test;
@@ -20,5 +21,6 @@
     offsetting_constant_value_visitor_test.main();
     navigation_test.main();
     completion_contributor_test.main();
+    file_tracker_test.main();
   }, name: 'Angular Plugin tests');
 }