diff --git a/test/angular_spec.dart b/test/angular_spec.dart index 69b427b72..555ec2b8b 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -2,6 +2,7 @@ library angular_spec; import '_specs.dart'; import 'package:angular/utils.dart'; +import 'dart:mirrors'; main() { describe('angular.dart unittests', () { @@ -40,4 +41,289 @@ main() { }).toThrow('Unknown function type, expecting 0 to 5 args.'); }); }); + + describe('angular symbols', () { + it('should not export symbols that we do not know about', () { + // Test is failing? Add new symbols to the "ALLOWED_NAMES" list below. + // But make sure that you intend to export the symbol! + // Questions? Talk to @jbdeboer + + _getSymbolsFromLibrary(String libraryName) { + var names = []; + var SYMBOL_NAME = new RegExp('"(.*)"'); + _unwrapSymbol(sym) => SYMBOL_NAME.firstMatch(sym.toString()).group(1); + + var seen = {}; + + // TODO(deboer): Add types once Dart VM 1.2 is deprecated. + extractSymbols(/* LibraryMirror */ lib) { + lib.declarations.forEach((symbol, _) { + names.add([_unwrapSymbol(symbol), _unwrapSymbol(lib.qualifiedName)]); + }); + + lib.libraryDependencies.forEach((/* LibraryDependencyMirror */ libDep) { + LibraryMirror target = libDep.targetLibrary; + if (!libDep.isExport) return; + if (!seen.containsKey(target)) { + seen[target] = true; + extractSymbols(target); + } + }); + }; + + var lib = currentMirrorSystem().findLibrary(new Symbol(libraryName)); + + extractSymbols(lib); + return names; + } + + var names; + try { // Not impleneted in Dart VM 1.2 + names = _getSymbolsFromLibrary("angular"); + } on UnimplementedError catch (e) { + return; // Not implemented, quietly skip. + } catch (e) { + print("Error: $e"); + return; // On VMes <1.2, quietly skip. + } + + var ALLOWED_PREFIXS = [ + "Ng", + "ng", + "Angular", + "_" + ]; + + // NOTE(deboer): There are a number of symbols that should not be + // exported in the list. We are working on un-export the symbols. + // Comments on each symbols below. + var ALLOWED_NAMES = [ + "di.FactoryFn", + "di.Injector", + "di.InvalidBindingError", + "di.Key", // common name, should be removed. + "di.TypeFactory", + "di.Visibility", + "di.NoProviderError", + "di.CircularDependencyError", + "di.ObjectFactory", + "di.Module", + "angular.core.AnnotationMap", + "angular.core.LruCache", // internal? + "angular.core.ScopeStats", + "angular.core.ArrayFn", // internal? + "angular.core.Interpolation", + "angular.core.LongStackTrace", // internal? + "angular.core.Cache", // internal? + "angular.core.ExpressionVisitor", // internal? + "angular.core.ScopeEvent", + "angular.core.MapFn", // internal? + "angular.core.EvalFunction1", // internal? + "angular.core.MetadataExtractor", // internal? + "angular.core.ExceptionHandler", + "angular.core.ZoneOnTurn", // internal? + "angular.core.ZoneOnError", // internal? + "angular.core.ScopeDigestTTL", + "angular.core.EvalFunction0", // internal? + "angular.core.AnnotationsMap", // internal? + "angular.core.RootScope", + "angular.core.CacheStats", + "angular.core.ScopeLocals", + "angular.core.ScopeStreamSubscription", + "angular.core.Interpolate", + "angular.core.NOT_IMPLEMENTED", // internal? + "angular.core.Scope", + "angular.core.AttrFieldAnnotation", + "angular.core.UnboundedCache", // internal? + "angular.core.ScopeStream", // internal? + "angular.core.FilterMap", // internal? + "angular.core.AstParser", // internal? + "angular.watch_group.FunctionApply", // internal? + "angular.watch_group.WatchGroup", // internal? + "angular.watch_group.ContextReferenceAST", // internal? + "angular.watch_group.ConstantAST", // internal? + "angular.watch_group.Watch", + "angular.watch_group.ReactionFn", // internal? + "angular.watch_group.ChangeLog", + "angular.watch_group.FieldReadAST", // internal? + "angular.watch_group.PureFunctionAST", // internal? + "angular.watch_group.PrototypeMap", // internal? + "angular.watch_group.CollectionAST", // internal? + "angular.watch_group.MethodAST", // internal? + "angular.watch_group.AST", // internal? + "angular.watch_group.RootWatchGroup", + "angular.core.dom.AnimationResult", + "angular.core.dom.WalkingViewFactory", // internal? + "angular.core.dom.ResponseError", + "angular.core.dom.View", + "angular.core.dom.ElementBinder", // internal? + "angular.core.dom.NoOpAnimation", + "angular.core.dom.AttributeChanged", + "angular.core.dom.HttpBackend", + "angular.core.dom.HttpDefaults", + "angular.core.dom.TaggedElementBinder", // internal? + "angular.core.dom.LocationWrapper", + "angular.core.dom.Cookies", + "angular.core.dom.ElementBinderTreeRef", // internal? + "angular.core.dom.EventHandler", + "angular.core.dom.Response", + "angular.core.dom.HttpDefaultHeaders", + "angular.core.dom.Animation", + "angular.core.dom.ViewPort", + "angular.core.dom.TemplateLoader", + "angular.core.dom.RequestErrorInterceptor", + "angular.core.dom.TaggedTextBinder", // internal? + "angular.core.dom.Http", + "angular.core.dom.BoundViewFactory", // internal? + "angular.core.dom.ElementBinderFactory", // internal? + "angular.core.dom.DirectiveMap", // internal? + "angular.core.dom.BrowserCookies", + "angular.core.dom.HttpInterceptor", + "angular.core.dom.cloneElements", // internal? + "angular.core.dom.EventFunction", // internal? + "angular.core.dom.RequestInterceptor", + "angular.core.dom.DefaultTransformDataHttpInterceptor", + "angular.core.dom.HttpResponseConfig", + "angular.core.dom.ElementProbe", + "angular.core.dom.ApplyMapping", // internal? + "angular.core.dom.ViewCache", // internal? + "angular.core.dom.FieldMetadataExtractor", // internal? + "angular.core.dom.Compiler", + "angular.core.dom.HttpResponse", + "angular.core.dom.UrlRewriter", + "angular.core.dom.DirectiveRef", + "angular.core.dom.HttpInterceptors", + "angular.core.dom.forceNewDirectivesAndFilters", // internal? + "angular.core.dom.DirectiveSelectorFactory", // internal? + "angular.core.dom.ObserverChanged", + "angular.core.dom.TaggingViewFactory", // internal? + "angular.core.dom.NodeCursor", // internal? + "angular.core.dom.TemplateCache", // internal? + "angular.core.dom.ViewFactory", + "angular.core.dom.NullTreeSanitizer", + "angular.core.dom.NodeAttrs", + "angular.core.dom.ElementBinderTree", // internal? + "angular.core.dom.WalkingCompiler", // internal? + "angular.core.dom.TaggingCompiler", // internal? + "angular.core.dom.DirectiveSelector", // internal? + "angular.core.parser.BoundGetter", // internal? + "angular.core.parser.LocalsWrapper", // internal? + "angular.core.parser.Getter", // common name + "angular.core.parser.Parser", + "angular.core.parser.ParserBackend", + "angular.core.parser.BoundSetter", + "angular.core.parser.Setter", // common name + "angular.core.parser.syntax.LiteralObject", // evenything in syntax should be private + "angular.core.parser.syntax.CallMember", + "angular.core.parser.syntax.Filter", + "angular.core.parser.syntax.defaultFilterMap", + "angular.core.parser.syntax.BoundExpression", + "angular.core.parser.syntax.AccessMember", + "angular.core.parser.syntax.Expression", + "angular.core.parser.syntax.AccessScope", + "angular.core.parser.syntax.Assign", + "angular.core.parser.syntax.AccessKeyed", + "angular.core.parser.syntax.CallScope", + "angular.core.parser.syntax.CallFunction", + "angular.core.parser.syntax.Conditional", + "angular.core.parser.syntax.Binary", + "angular.core.parser.syntax.Chain", + "angular.core.parser.syntax.Prefix", + "angular.core.parser.syntax.Literal", + "angular.core.parser.syntax.LiteralString", + "angular.core.parser.syntax.LiteralArray", + "angular.core.parser.syntax.LiteralPrimitive", + "angular.core.parser.syntax.Visitor", + "angular.core.parser.dynamic_parser.DynamicExpression", + "angular.core.parser.dynamic_parser.ClosureMap", + "angular.core.parser.dynamic_parser.DynamicParser", + "angular.core.parser.dynamic_parser.DynamicParserBackend", + "angular.core.parser.static_parser.StaticParserFunctions", + "angular.core.parser.static_parser.StaticExpression", + "angular.core.parser.static_parser.StaticParser", + "angular.core.parser.lexer.Scanner", // everything in lexer should be private + "angular.core.parser.lexer.OPERATORS", + "angular.core.parser.lexer.NumberToken", + "angular.core.parser.lexer.Token", + "angular.core.parser.lexer.IdentifierToken", + "angular.core.parser.lexer.StringToken", + "angular.core.parser.lexer.CharacterToken", + "angular.core.parser.lexer.Lexer", + "angular.core.parser.lexer.KEYWORDS", + "angular.core.parser.lexer.OperatorToken", + "angular.directive.ItemEval", + "angular.directive.OptionValueDirective", + "angular.directive.InputSelectDirective", + "angular.directive.InputTextLikeDirective", + "angular.directive.InputNumberLikeDirective", + "angular.directive.ContentEditableDirective", + "angular.directive.InputCheckboxDirective", + "angular.directive.InputRadioDirective", + "angular.filter.JsonFilter", + "angular.filter.Equals", + "angular.filter.Mapper", + "angular.filter.FilterFilter", + "angular.filter.NumberFilter", + "angular.filter.DateFilter", + "angular.filter.LowercaseFilter", + "angular.filter.UppercaseFilter", + "angular.filter.OrderByFilter", + "angular.filter.CurrencyFilter", + "angular.filter.LimitToFilter", + "angular.filter.Predicate", + "angular.routing.RouteInitializerFn", + "angular.routing.RouteProvider", + "angular.routing.RouteInitializer", + "angular.routing.RouteViewFactory", + "route.client.RouteHandle", + "route.client.RouteEnterEvent", + "route.client.RouteStartEvent", + "route.client.Router", + "route.client.RouteEvent", + "route.client.RouteLeaveEventHandler", + "route.client.Route", + "route.client.RouteImpl", + "route.client.RouteLeaveEvent", + "route.client.RoutePreEnterEventHandler", + "route.client.RoutePreEnterEvent", + "route.client.Routable", + "route.client.RouteEnterEventHandler", + "url_matcher.UrlMatcher", + "url_matcher.UrlMatch", + "dirty_checking_change_detector.FieldGetter", // everything in change detector should be private + "dirty_checking_change_detector.DirtyCheckingChangeDetector", + "dirty_checking_change_detector.DirtyCheckingRecord", + "dirty_checking_change_detector.ItemRecord", + "dirty_checking_change_detector.KeyValueRecord", + "dirty_checking_change_detector.DuplicateMap", + "dirty_checking_change_detector.GetterCache", + "dirty_checking_change_detector.DirtyCheckingChangeDetectorGroup" + ]; + + var _nameMap = {}; + ALLOWED_NAMES.forEach((x) => _nameMap[x] = true); + + assertSymbolNameIsOk(List nameInfo) { + String name = nameInfo[0]; + String libName = nameInfo[1]; + + if (ALLOWED_PREFIXS.any((prefix) => name.startsWith(prefix))) return; + + var key = "$libName.$name"; + if (_nameMap.containsKey(key)) { + _nameMap[key] = false; + return; + } + + throw "Symbol $key is exported thru the angular library, but it shouldn't be"; + }; + + names.forEach(assertSymbolNameIsOk); + + // If there are keys that no longer need to be in the ALLOWED_NAMES list, complain. + _nameMap.forEach((k,v) { + if (v) print("angular_spec.dart: Unused ALLOWED_NAMES key $k"); + }); + }); + }); }