feat(transformers): changes transformers to collect information about providers and resolve identifiers during linking

Closes #7380
This commit is contained in:
vsavkin
2016-04-04 10:32:28 -07:00
committed by Victor Savkin
parent bd8a4215dd
commit 4e9809bcb2
45 changed files with 2424 additions and 979 deletions

View File

@ -833,136 +833,138 @@ const COMMON = [
'var workaround_empty_observable_list_diff:any'
];
const COMPILER = [
'AttrAst',
'AttrAst.constructor(name:string, value:string, sourceSpan:ParseSourceSpan)',
'AttrAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundDirectivePropertyAst',
'BoundDirectivePropertyAst.constructor(directiveName:string, templateName:string, value:AST, sourceSpan:ParseSourceSpan)',
'BoundDirectivePropertyAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundElementPropertyAst',
'BoundElementPropertyAst.constructor(name:string, type:PropertyBindingType, value:AST, unit:string, sourceSpan:ParseSourceSpan)',
'BoundElementPropertyAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundEventAst',
'BoundEventAst.constructor(name:string, target:string, handler:AST, sourceSpan:ParseSourceSpan)',
'BoundEventAst.fullName:any',
'BoundEventAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundTextAst',
'BoundTextAst.constructor(value:AST, ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'BoundTextAst.visit(visitor:TemplateAstVisitor, context:any):any',
'CompileDirectiveMetadata',
'CompileDirectiveMetadata.changeDetection:ChangeDetectionStrategy',
'CompileDirectiveMetadata.constructor({type,isComponent,dynamicLoadable,selector,exportAs,changeDetection,inputs,outputs,hostListeners,hostProperties,hostAttributes,lifecycleHooks,providers,viewProviders,queries,viewQueries,template}:{type?:CompileTypeMetadata, isComponent?:boolean, dynamicLoadable?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:{[key:string]:string}, outputs?:{[key:string]:string}, hostListeners?:{[key:string]:string}, hostProperties?:{[key:string]:string}, hostAttributes?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>, viewProviders?:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], template?:CompileTemplateMetadata})',
'CompileDirectiveMetadata.create({type,isComponent,dynamicLoadable,selector,exportAs,changeDetection,inputs,outputs,host,lifecycleHooks,providers,viewProviders,queries,viewQueries,template}:{type?:CompileTypeMetadata, isComponent?:boolean, dynamicLoadable?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:string[], outputs?:string[], host?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>, viewProviders?:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], template?:CompileTemplateMetadata}):CompileDirectiveMetadata',
'CompileDirectiveMetadata.providers:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>',
'CompileDirectiveMetadata.queries:CompileQueryMetadata[]',
'CompileDirectiveMetadata.viewProviders:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>',
'CompileDirectiveMetadata.viewQueries:CompileQueryMetadata[]',
'CompileDirectiveMetadata.dynamicLoadable:boolean',
'CompileDirectiveMetadata.exportAs:string',
'CompileDirectiveMetadata.fromJson(data:{[key:string]:any}):CompileDirectiveMetadata',
'CompileDirectiveMetadata.hostAttributes:{[key:string]:string}',
'CompileDirectiveMetadata.hostListeners:{[key:string]:string}',
'CompileDirectiveMetadata.hostProperties:{[key:string]:string}',
'CompileDirectiveMetadata.inputs:{[key:string]:string}',
'CompileDirectiveMetadata.isComponent:boolean',
'CompileDirectiveMetadata.lifecycleHooks:LifecycleHooks[]',
'CompileDirectiveMetadata.outputs:{[key:string]:string}',
'CompileDirectiveMetadata.selector:string',
'CompileDirectiveMetadata.template:CompileTemplateMetadata',
'CompileDirectiveMetadata.toJson():{[key:string]:any}',
'CompileDirectiveMetadata.type:CompileTypeMetadata',
'CompileDirectiveMetadata.identifier:CompileIdentifierMetadata',
'CompileTemplateMetadata',
'CompileTemplateMetadata.constructor({encapsulation,template,templateUrl,styles,styleUrls,ngContentSelectors}:{encapsulation?:ViewEncapsulation, template?:string, templateUrl?:string, styles?:string[], styleUrls?:string[], ngContentSelectors?:string[]})',
'CompileTemplateMetadata.encapsulation:ViewEncapsulation',
'CompileTemplateMetadata.fromJson(data:{[key:string]:any}):CompileTemplateMetadata',
'CompileTemplateMetadata.ngContentSelectors:string[]',
'CompileTemplateMetadata.styleUrls:string[]',
'CompileTemplateMetadata.styles:string[]',
'CompileTemplateMetadata.template:string',
'CompileTemplateMetadata.templateUrl:string',
'CompileTemplateMetadata.toJson():{[key:string]:any}',
'CompileTypeMetadata',
'CompileTypeMetadata.constructor({runtime,name,moduleUrl,prefix,isHost,constConstructor,diDeps}:{runtime?:Type, name?:string, moduleUrl?:string, prefix?:string, isHost?:boolean, constConstructor?:boolean, diDeps?:CompileDiDependencyMetadata[]})',
'CompileTypeMetadata.fromJson(data:{[key:string]:any}):CompileTypeMetadata',
'CompileTypeMetadata.isHost:boolean',
'CompileTypeMetadata.moduleUrl:string',
'CompileTypeMetadata.name:string',
'CompileTypeMetadata.runtime:Type',
'CompileTypeMetadata.toJson():{[key:string]:any}',
'CompileTypeMetadata.diDeps:CompileDiDependencyMetadata[]',
'CompileTypeMetadata.prefix:string',
'CompileTypeMetadata.constConstructor:boolean',
'CompileTypeMetadata.identifier:CompileIdentifierMetadata',
'CompileTypeMetadata.type:CompileTypeMetadata',
'DirectiveAst',
'DirectiveAst.constructor(directive:CompileDirectiveMetadata, inputs:BoundDirectivePropertyAst[], hostProperties:BoundElementPropertyAst[], hostEvents:BoundEventAst[], exportAsVars:VariableAst[], sourceSpan:ParseSourceSpan)',
'DirectiveAst.visit(visitor:TemplateAstVisitor, context:any):any',
'ElementAst',
'ElementAst.constructor(name:string, attrs:AttrAst[], inputs:BoundElementPropertyAst[], outputs:BoundEventAst[], exportAsVars:VariableAst[], directives:DirectiveAst[], children:TemplateAst[], ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'ElementAst.getComponent():CompileDirectiveMetadata',
'ElementAst.isBound():boolean',
'ElementAst.visit(visitor:TemplateAstVisitor, context:any):any',
'EmbeddedTemplateAst',
'EmbeddedTemplateAst.constructor(attrs:AttrAst[], outputs:BoundEventAst[], vars:VariableAst[], directives:DirectiveAst[], children:TemplateAst[], ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'EmbeddedTemplateAst.visit(visitor:TemplateAstVisitor, context:any):any',
'NgContentAst',
'NgContentAst.constructor(index:number, ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'NgContentAst.visit(visitor:TemplateAstVisitor, context:any):any',
'PropertyBindingType',
'PropertyBindingType.Attribute',
'PropertyBindingType.Class',
'PropertyBindingType.Property',
'PropertyBindingType.Style',
'SourceModule',
'SourceModule.constructor(moduleUrl:string, sourceWithModuleRefs:string)',
'SourceModule.getSourceWithImports():SourceWithImports',
'SourceModule.getSourceWithoutImports(sourceWithModuleRefs:string):string',
'SourceWithImports',
'SourceWithImports.constructor(source:string, imports:string[][])',
'TemplateAst',
'TemplateAst.sourceSpan:ParseSourceSpan',
'TemplateAst.visit(visitor:TemplateAstVisitor, context:any):any',
'TemplateAstVisitor',
'TemplateAstVisitor.visitAttr(ast:AttrAst, context:any):any',
'TemplateAstVisitor.visitBoundText(ast:BoundTextAst, context:any):any',
'TemplateAstVisitor.visitDirective(ast:DirectiveAst, context:any):any',
'TemplateAstVisitor.visitDirectiveProperty(ast:BoundDirectivePropertyAst, context:any):any',
'TemplateAstVisitor.visitElement(ast:ElementAst, context:any):any',
'TemplateAstVisitor.visitElementProperty(ast:BoundElementPropertyAst, context:any):any',
'TemplateAstVisitor.visitEmbeddedTemplate(ast:EmbeddedTemplateAst, context:any):any',
'TemplateAstVisitor.visitEvent(ast:BoundEventAst, context:any):any',
'TemplateAstVisitor.visitNgContent(ast:NgContentAst, context:any):any',
'TemplateAstVisitor.visitText(ast:TextAst, context:any):any',
'TemplateAstVisitor.visitVariable(ast:VariableAst, context:any):any',
'TemplateCompiler',
'TemplateCompiler.clearCache():any',
'TemplateCompiler.compileHostComponentRuntime(type:Type):Promise<HostViewFactory>',
'TemplateCompiler.compileStylesheetCodeGen(stylesheetUrl:string, cssText:string):SourceModule[]',
'TemplateCompiler.compileTemplatesCodeGen(components:NormalizedComponentWithViewDirectives[]):SourceModule',
'TemplateCompiler.constructor(_runtimeMetadataResolver:RuntimeMetadataResolver, _templateNormalizer:TemplateNormalizer, _templateParser:TemplateParser, _styleCompiler:StyleCompiler, _cdCompiler:ChangeDetectionCompiler, _protoViewCompiler:ProtoViewCompiler, _viewCompiler:ViewCompiler, _resolvedMetadataCache:ResolvedMetadataCache, _genConfig:ChangeDetectorGenConfig)',
'TemplateCompiler.normalizeDirectiveMetadata(directive:CompileDirectiveMetadata):Promise<CompileDirectiveMetadata>',
'TextAst',
'TextAst.constructor(value:string, ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'TextAst.visit(visitor:TemplateAstVisitor, context:any):any',
'UrlResolver',
'UrlResolver.constructor(packagePrefix:string)',
'UrlResolver.resolve(baseUrl:string, url:string):string',
'VariableAst',
'VariableAst.constructor(name:string, value:string, sourceSpan:ParseSourceSpan)',
'VariableAst.visit(visitor:TemplateAstVisitor, context:any):any',
'XHR',
'XHR.get(url:string):Promise<string>',
'const COMPILER_PROVIDERS:Array<Type|Provider|any[]>',
'const PLATFORM_DIRECTIVES:OpaqueToken',
'const PLATFORM_PIPES:OpaqueToken',
'const TEMPLATE_TRANSFORMS:any',
'createWithoutPackagePrefix():UrlResolver',
'getUrlScheme(url:string):string',
'templateVisitAll(visitor:TemplateAstVisitor, asts:TemplateAst[], context:any):any[]',
'var DEFAULT_PACKAGE_URL_PROVIDER:any'
];
const COMPILER =
[
'AttrAst',
'AttrAst.constructor(name:string, value:string, sourceSpan:ParseSourceSpan)',
'AttrAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundDirectivePropertyAst',
'BoundDirectivePropertyAst.constructor(directiveName:string, templateName:string, value:AST, sourceSpan:ParseSourceSpan)',
'BoundDirectivePropertyAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundElementPropertyAst',
'BoundElementPropertyAst.constructor(name:string, type:PropertyBindingType, value:AST, unit:string, sourceSpan:ParseSourceSpan)',
'BoundElementPropertyAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundEventAst',
'BoundEventAst.constructor(name:string, target:string, handler:AST, sourceSpan:ParseSourceSpan)',
'BoundEventAst.fullName:any',
'BoundEventAst.visit(visitor:TemplateAstVisitor, context:any):any',
'BoundTextAst',
'BoundTextAst.constructor(value:AST, ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'BoundTextAst.visit(visitor:TemplateAstVisitor, context:any):any',
'CompileDirectiveMetadata',
'CompileDirectiveMetadata.changeDetection:ChangeDetectionStrategy',
'CompileDirectiveMetadata.constructor({type,isComponent,dynamicLoadable,selector,exportAs,changeDetection,inputs,outputs,hostListeners,hostProperties,hostAttributes,lifecycleHooks,providers,viewProviders,queries,viewQueries,template}:{type?:CompileTypeMetadata, isComponent?:boolean, dynamicLoadable?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:{[key:string]:string}, outputs?:{[key:string]:string}, hostListeners?:{[key:string]:string}, hostProperties?:{[key:string]:string}, hostAttributes?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, viewProviders?:Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], template?:CompileTemplateMetadata})',
'CompileDirectiveMetadata.create({type,isComponent,dynamicLoadable,selector,exportAs,changeDetection,inputs,outputs,host,lifecycleHooks,providers,viewProviders,queries,viewQueries,template}:{type?:CompileTypeMetadata, isComponent?:boolean, dynamicLoadable?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:string[], outputs?:string[], host?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, viewProviders?:Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], template?:CompileTemplateMetadata}):CompileDirectiveMetadata',
'CompileDirectiveMetadata.providers:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>',
'CompileDirectiveMetadata.queries:CompileQueryMetadata[]',
'CompileDirectiveMetadata.viewProviders:Array<CompileProviderMetadata|CompileTypeMetadata|any[]>',
'CompileDirectiveMetadata.viewQueries:CompileQueryMetadata[]',
'CompileDirectiveMetadata.dynamicLoadable:boolean',
'CompileDirectiveMetadata.exportAs:string',
'CompileDirectiveMetadata.fromJson(data:{[key:string]:any}):CompileDirectiveMetadata',
'CompileDirectiveMetadata.hostAttributes:{[key:string]:string}',
'CompileDirectiveMetadata.hostListeners:{[key:string]:string}',
'CompileDirectiveMetadata.hostProperties:{[key:string]:string}',
'CompileDirectiveMetadata.inputs:{[key:string]:string}',
'CompileDirectiveMetadata.isComponent:boolean',
'CompileDirectiveMetadata.lifecycleHooks:LifecycleHooks[]',
'CompileDirectiveMetadata.outputs:{[key:string]:string}',
'CompileDirectiveMetadata.selector:string',
'CompileDirectiveMetadata.template:CompileTemplateMetadata',
'CompileDirectiveMetadata.toJson():{[key:string]:any}',
'CompileDirectiveMetadata.type:CompileTypeMetadata',
'CompileDirectiveMetadata.identifier:CompileIdentifierMetadata',
'CompileTemplateMetadata',
'CompileTemplateMetadata.constructor({encapsulation,template,templateUrl,styles,styleUrls,ngContentSelectors}:{encapsulation?:ViewEncapsulation, template?:string, templateUrl?:string, styles?:string[], styleUrls?:string[], ngContentSelectors?:string[]})',
'CompileTemplateMetadata.encapsulation:ViewEncapsulation',
'CompileTemplateMetadata.fromJson(data:{[key:string]:any}):CompileTemplateMetadata',
'CompileTemplateMetadata.ngContentSelectors:string[]',
'CompileTemplateMetadata.styleUrls:string[]',
'CompileTemplateMetadata.styles:string[]',
'CompileTemplateMetadata.template:string',
'CompileTemplateMetadata.templateUrl:string',
'CompileTemplateMetadata.toJson():{[key:string]:any}',
'CompileTypeMetadata',
'CompileTypeMetadata.constructor({runtime,name,moduleUrl,prefix,isHost,constConstructor,value,diDeps}:{runtime?:Type, name?:string, moduleUrl?:string, prefix?:string, isHost?:boolean, constConstructor?:boolean, value?:any, diDeps?:CompileDiDependencyMetadata[]})',
'CompileTypeMetadata.fromJson(data:{[key:string]:any}):CompileTypeMetadata',
'CompileTypeMetadata.value:any',
'CompileTypeMetadata.isHost:boolean',
'CompileTypeMetadata.moduleUrl:string',
'CompileTypeMetadata.name:string',
'CompileTypeMetadata.runtime:Type',
'CompileTypeMetadata.toJson():{[key:string]:any}',
'CompileTypeMetadata.diDeps:CompileDiDependencyMetadata[]',
'CompileTypeMetadata.prefix:string',
'CompileTypeMetadata.constConstructor:boolean',
'CompileTypeMetadata.identifier:CompileIdentifierMetadata',
'CompileTypeMetadata.type:CompileTypeMetadata',
'DirectiveAst',
'DirectiveAst.constructor(directive:CompileDirectiveMetadata, inputs:BoundDirectivePropertyAst[], hostProperties:BoundElementPropertyAst[], hostEvents:BoundEventAst[], exportAsVars:VariableAst[], sourceSpan:ParseSourceSpan)',
'DirectiveAst.visit(visitor:TemplateAstVisitor, context:any):any',
'ElementAst',
'ElementAst.constructor(name:string, attrs:AttrAst[], inputs:BoundElementPropertyAst[], outputs:BoundEventAst[], exportAsVars:VariableAst[], directives:DirectiveAst[], children:TemplateAst[], ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'ElementAst.getComponent():CompileDirectiveMetadata',
'ElementAst.isBound():boolean',
'ElementAst.visit(visitor:TemplateAstVisitor, context:any):any',
'EmbeddedTemplateAst',
'EmbeddedTemplateAst.constructor(attrs:AttrAst[], outputs:BoundEventAst[], vars:VariableAst[], directives:DirectiveAst[], children:TemplateAst[], ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'EmbeddedTemplateAst.visit(visitor:TemplateAstVisitor, context:any):any',
'NgContentAst',
'NgContentAst.constructor(index:number, ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'NgContentAst.visit(visitor:TemplateAstVisitor, context:any):any',
'PropertyBindingType',
'PropertyBindingType.Attribute',
'PropertyBindingType.Class',
'PropertyBindingType.Property',
'PropertyBindingType.Style',
'SourceModule',
'SourceModule.constructor(moduleUrl:string, sourceWithModuleRefs:string)',
'SourceModule.getSourceWithImports():SourceWithImports',
'SourceModule.getSourceWithoutImports(sourceWithModuleRefs:string):string',
'SourceWithImports',
'SourceWithImports.constructor(source:string, imports:string[][])',
'TemplateAst',
'TemplateAst.sourceSpan:ParseSourceSpan',
'TemplateAst.visit(visitor:TemplateAstVisitor, context:any):any',
'TemplateAstVisitor',
'TemplateAstVisitor.visitAttr(ast:AttrAst, context:any):any',
'TemplateAstVisitor.visitBoundText(ast:BoundTextAst, context:any):any',
'TemplateAstVisitor.visitDirective(ast:DirectiveAst, context:any):any',
'TemplateAstVisitor.visitDirectiveProperty(ast:BoundDirectivePropertyAst, context:any):any',
'TemplateAstVisitor.visitElement(ast:ElementAst, context:any):any',
'TemplateAstVisitor.visitElementProperty(ast:BoundElementPropertyAst, context:any):any',
'TemplateAstVisitor.visitEmbeddedTemplate(ast:EmbeddedTemplateAst, context:any):any',
'TemplateAstVisitor.visitEvent(ast:BoundEventAst, context:any):any',
'TemplateAstVisitor.visitNgContent(ast:NgContentAst, context:any):any',
'TemplateAstVisitor.visitText(ast:TextAst, context:any):any',
'TemplateAstVisitor.visitVariable(ast:VariableAst, context:any):any',
'TemplateCompiler',
'TemplateCompiler.clearCache():any',
'TemplateCompiler.compileHostComponentRuntime(type:Type):Promise<HostViewFactory>',
'TemplateCompiler.compileStylesheetCodeGen(stylesheetUrl:string, cssText:string):SourceModule[]',
'TemplateCompiler.compileTemplatesCodeGen(components:NormalizedComponentWithViewDirectives[]):SourceModule',
'TemplateCompiler.constructor(_runtimeMetadataResolver:RuntimeMetadataResolver, _templateNormalizer:TemplateNormalizer, _templateParser:TemplateParser, _styleCompiler:StyleCompiler, _cdCompiler:ChangeDetectionCompiler, _protoViewCompiler:ProtoViewCompiler, _viewCompiler:ViewCompiler, _resolvedMetadataCache:ResolvedMetadataCache, _genConfig:ChangeDetectorGenConfig)',
'TemplateCompiler.normalizeDirectiveMetadata(directive:CompileDirectiveMetadata):Promise<CompileDirectiveMetadata>',
'TextAst',
'TextAst.constructor(value:string, ngContentIndex:number, sourceSpan:ParseSourceSpan)',
'TextAst.visit(visitor:TemplateAstVisitor, context:any):any',
'UrlResolver',
'UrlResolver.constructor(packagePrefix:string)',
'UrlResolver.resolve(baseUrl:string, url:string):string',
'VariableAst',
'VariableAst.constructor(name:string, value:string, sourceSpan:ParseSourceSpan)',
'VariableAst.visit(visitor:TemplateAstVisitor, context:any):any',
'XHR',
'XHR.get(url:string):Promise<string>',
'const COMPILER_PROVIDERS:Array<Type|Provider|any[]>',
'const PLATFORM_DIRECTIVES:OpaqueToken',
'const PLATFORM_PIPES:OpaqueToken',
'const TEMPLATE_TRANSFORMS:any',
'createWithoutPackagePrefix():UrlResolver',
'getUrlScheme(url:string):string',
'templateVisitAll(visitor:TemplateAstVisitor, asts:TemplateAst[], context:any):any[]',
'var DEFAULT_PACKAGE_URL_PROVIDER:any'
];
const INSTRUMENTATION = [
'WtfScopeFn',
@ -1133,7 +1135,18 @@ describe('public API', () => {
function checkPublicApi(file: string, expected: string[]) {
const sortedActual = publicApi(file).sort();
const sortedExpected = expected.sort();
const missing = sortedActual.filter((i) => sortedExpected.indexOf(i) < 0).map(s => `+${s}`);
const extra = sortedExpected.filter((i) => sortedActual.indexOf(i) < 0).map(s => `-${s}`);
expect(missing.concat(extra)).toEqual([]);
const missing = sortedActual.filter((i) => sortedExpected.indexOf(i) < 0);
const extra = sortedExpected.filter((i) => sortedActual.indexOf(i) < 0);
if (missing.length > 0) {
console.log("Missing:");
missing.forEach((m) => console.log(m));
}
if (extra.length > 0) {
console.log("Extra:");
extra.forEach((m) => console.log(m));
}
expect(missing.map(s => `+${s}`).concat(extra.map(s => `-${s}`))).toEqual([]);
}