fix(ivy): ngcc - teach Esm5ReflectionHost about aliased variables (#29092)

Sometimes, in ESM5 code, aliases to exported variables are used internally
to refer to the exported value. This prevented some analysis from being
able to match up a reference to an export to the actual export itself.

For example in the following code:

```
var HttpClientXsrfModule = /** @class */ (function () {
  function HttpClientXsrfModule() {
  }
  HttpClientXsrfModule_1 = HttpClientXsrfModule;
  HttpClientXsrfModule.withOptions = function (options) {
      if (options === void 0) { options = {}; }
      return {
          ngModule: HttpClientXsrfModule_1,
          providers: [],
      };
  };
  var HttpClientXsrfModule_1;
  HttpClientXsrfModule = HttpClientXsrfModule_1 = tslib_1.__decorate([
      NgModule({
          providers: [],
      })
  ], HttpClientXsrfModule);
  return HttpClientXsrfModule;
}());
```

We were not able to tell that the `ngModule: HttpClientXsrfModule_1` property
assignment was actually meant to refer to the `function HttpClientXrsfModule()`
declaration.  This caused the `ModuleWithProviders` processing to fail.

This commit ensures that we can compile typings files using the ESM5
format, so we can now update the examples boilerplate tool so that it
does not need to compile the ESM2015 format at all.

PR Close #29092
This commit is contained in:
Pete Bacon Darwin
2019-03-20 13:47:59 +00:00
committed by Matias Niemelä
parent b48d6e1b13
commit bdcbd9ed4b
3 changed files with 56 additions and 7 deletions

View File

@ -14,6 +14,7 @@ import {getNameText, hasNameIdentifier} from '../utils';
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
/**
* ESM5 packages contain ECMAScript IIFE functions that act like classes. For example:
*
@ -117,8 +118,30 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
id = outerClassNode.name;
}
// Resolve the identifier to a Symbol, and return the declaration of that.
return super.getDeclarationOfIdentifier(id);
const declaration = super.getDeclarationOfIdentifier(id);
if (!declaration || !ts.isVariableDeclaration(declaration.node) ||
declaration.node.initializer !== undefined ||
// VariableDeclaration => VariableDeclarationList => VariableStatement => IIFE Block
!ts.isBlock(declaration.node.parent.parent.parent)) {
return declaration;
}
// We might have an alias to another variable declaration.
// Search the containing iife body for it.
const block = declaration.node.parent.parent.parent;
const aliasSymbol = this.checker.getSymbolAtLocation(declaration.node.name);
for (let i = 0; i < block.statements.length; i++) {
const statement = block.statements[i];
// Looking for statement that looks like: `AliasedVariable = OriginalVariable;`
if (isAssignmentStatement(statement) && ts.isIdentifier(statement.expression.left) &&
ts.isIdentifier(statement.expression.right) &&
this.checker.getSymbolAtLocation(statement.expression.left) === aliasSymbol) {
return this.getDeclarationOfIdentifier(statement.expression.right);
}
}
return declaration;
}
/**

View File

@ -371,6 +371,18 @@ describe('Esm5ReflectionHost [import helper style]', () => {
expect(actualDeclaration !.node).toBe(expectedDeclarationNode);
expect(actualDeclaration !.viaModule).toBe('@angular/core');
});
it('should find the "actual" declaration of an aliased variable identifier', () => {
const program = makeTestProgram(fileSystem.files[2]);
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const ngModuleRef = findIdentifier(
program.getSourceFile(fileSystem.files[2].name) !, 'HttpClientXsrfModule_1',
isNgModulePropertyAssignment);
const declaration = host.getDeclarationOfIdentifier(ngModuleRef !);
expect(declaration).not.toBe(null);
expect(declaration !.node.getText()).toContain('function HttpClientXsrfModule()');
});
});
});
@ -422,3 +434,20 @@ describe('Esm5ReflectionHost [import helper style]', () => {
return node.forEachChild(node => findVariableDeclaration(node, variableName));
}
});
function findIdentifier(
node: ts.Node | undefined, identifierName: string,
requireFn: (node: ts.Identifier) => boolean): ts.Identifier|undefined {
if (!node) {
return undefined;
}
if (ts.isIdentifier(node) && node.text === identifierName && requireFn(node)) {
return node;
}
return node.forEachChild(node => findIdentifier(node, identifierName, requireFn));
}
function isNgModulePropertyAssignment(identifier: ts.Identifier): boolean {
return ts.isPropertyAssignment(identifier.parent) &&
identifier.parent.name.getText() === 'ngModule';
}