library angular2.transform.common.class_matcher_base; import 'package:analyzer/src/generated/ast.dart'; import 'package:barback/barback.dart' show AssetId; import 'package:code_transformers/assets.dart'; import 'package:path/path.dart' as path; import 'logging.dart' show logger; /// Checks if a given [Identifier] matches any of the given [ClassDescriptor]s. abstract class ClassMatcherBase { /// Always start out with the default angular [ClassDescriptor]s. final List _classDescriptors; ClassMatcherBase(this._classDescriptors); /// Adds a new [ClassDescriptor]. void add(ClassDescriptor classDescriptor) => _classDescriptors.add(classDescriptor); /// Adds a number of [ClassDescriptor]s. void addAll(Iterable classDescriptors) => _classDescriptors.addAll(classDescriptors); /// Returns the first [ClassDescriptor] that matches the given /// [Identifier] node which appears in `assetId`. ClassDescriptor firstMatch(Identifier className, AssetId assetId) => _classDescriptors.firstWhere((a) => isMatch(className, a, assetId), orElse: () => null); /// Checks whether an [Identifier] matches any [ClassDescriptor]. bool hasMatch(Identifier className, AssetId assetId) => _classDescriptors.any((a) => isMatch(className, a, assetId)); /// Checks whether an [Identifier] matches any [ClassDescriptor]. ImportDirective getMatchingImport(Identifier className, AssetId assetId) { for (var d in _classDescriptors) { var matchingImport = _getMatchingImport(className, d, assetId); if (matchingImport != null) { return matchingImport; } } return null; } /// Checks if `descriptor` extends or is any of the supplied `interfaces`. bool implements(ClassDescriptor descriptor, List interfaces, {String missingSuperClassWarning}) { if (descriptor == null) return false; if (interfaces.contains(descriptor)) return true; if (descriptor.superClass == null) return false; var superClass = _classDescriptors.firstWhere( (a) => a.name == descriptor.superClass, orElse: () => null); if (superClass == null) { if (missingSuperClassWarning != null && missingSuperClassWarning.isNotEmpty) { logger.warning(missingSuperClassWarning); } return false; } return implements(superClass, interfaces); } } // Returns an [ImportDirective] matching `descriptor` for `className` which appears in `assetId`, or `null` if none exists. ImportDirective _getMatchingImport( Identifier className, ClassDescriptor descriptor, AssetId assetId) { if (className == null) return null; String name; Identifier prefix; if (className is PrefixedIdentifier) { name = className.identifier.name; prefix = className.prefix; } else { name = className.name; } if (name != descriptor.name) return null; return (className.root as CompilationUnit).directives .where((d) => d is ImportDirective) .firstWhere((ImportDirective i) { var importMatch = false; var uriString = i.uri.stringValue; if (uriString == descriptor.import) { importMatch = true; } else if (uriString.startsWith('package:') || uriString.startsWith('dart:')) { return false; } else { importMatch = descriptor.assetId == uriToAssetId(assetId, uriString, logger, null); } if (!importMatch) return false; if (prefix == null) return i.prefix == null; if (i.prefix == null) return false; return prefix.name == i.prefix.name; }, orElse: () => null); } // Checks if `className` which appears in `assetId` matches a [ClassDescriptor]. bool isMatch( Identifier className, ClassDescriptor descriptor, AssetId assetId) { return _getMatchingImport(className, descriptor, assetId) != null; } /// String based description of a class and its location. class ClassDescriptor { /// The name of the class. final String name; /// A `package:` style import path to the file where the class is defined. final String import; /// The class that this class extends or implements. This is the only optional /// field. final String superClass; AssetId get assetId => new AssetId(package, packagePath); String get package => path.split(import.replaceFirst('package:', '')).first; String get packagePath => path.joinAll(['lib'] ..addAll(path.split(import.replaceFirst('package:', ''))..removeAt(0))); const ClassDescriptor(this.name, this.import, {this.superClass}); }