2018-11-21 09:05:27 +00:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2018-11-21 09:05:27 +00:00
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
|
*/
|
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
2020-04-06 08:30:08 +01:00
|
|
|
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
2020-09-29 20:42:20 +01:00
|
|
|
import {Declaration} from '../../../src/ngtsc/reflection';
|
2018-11-21 09:05:27 +00:00
|
|
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
|
|
|
|
import {hasNameIdentifier, isDefined} from '../utils';
|
2020-04-06 08:30:08 +01:00
|
|
|
|
2018-12-13 11:52:20 -08:00
|
|
|
import {NgccReferencesRegistry} from './ngcc_references_registry';
|
2018-11-21 09:05:27 +00:00
|
|
|
|
|
|
|
|
export interface ExportInfo {
|
|
|
|
|
identifier: string;
|
2019-04-28 20:47:57 +01:00
|
|
|
from: AbsoluteFsPath;
|
|
|
|
|
dtsFrom?: AbsoluteFsPath|null;
|
2018-11-21 09:05:27 +00:00
|
|
|
}
|
|
|
|
|
export type PrivateDeclarationsAnalyses = ExportInfo[];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This class will analyze a program to find all the declared classes
|
|
|
|
|
* (i.e. on an NgModule) that are not publicly exported via an entry-point.
|
|
|
|
|
*/
|
|
|
|
|
export class PrivateDeclarationsAnalyzer {
|
2018-12-13 11:52:20 -08:00
|
|
|
constructor(
|
|
|
|
|
private host: NgccReflectionHost, private referencesRegistry: NgccReferencesRegistry) {}
|
2018-11-21 09:05:27 +00:00
|
|
|
|
|
|
|
|
analyzeProgram(program: ts.Program): PrivateDeclarationsAnalyses {
|
|
|
|
|
const rootFiles = this.getRootFiles(program);
|
|
|
|
|
return this.getPrivateDeclarations(rootFiles, this.referencesRegistry.getDeclarationMap());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getRootFiles(program: ts.Program): ts.SourceFile[] {
|
|
|
|
|
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getPrivateDeclarations(
|
|
|
|
|
rootFiles: ts.SourceFile[],
|
2020-09-29 20:42:20 +01:00
|
|
|
declarations: Map<ts.Identifier, Declaration>): PrivateDeclarationsAnalyses {
|
|
|
|
|
const privateDeclarations: Map<ts.Identifier, Declaration> = new Map(declarations);
|
2019-02-14 18:59:46 +01:00
|
|
|
|
2018-11-21 09:05:27 +00:00
|
|
|
rootFiles.forEach(f => {
|
|
|
|
|
const exports = this.host.getExportsOfModule(f);
|
|
|
|
|
if (exports) {
|
|
|
|
|
exports.forEach((declaration, exportedName) => {
|
fix(ivy): in ngcc, handle inline exports in commonjs code (#32129)
One of the compiler's tasks is to enumerate the exports of a given ES
module. This can happen for example to resolve `foo.bar` where `foo` is a
namespace import:
```typescript
import * as foo from './foo';
@NgModule({
directives: [foo.DIRECTIVES],
})
```
In this case, the compiler must enumerate the exports of `foo.ts` in order
to evaluate the expression `foo.DIRECTIVES`.
When this operation occurs under ngcc, it must deal with the different
module formats and types of exports that occur. In commonjs code, a problem
arises when certain exports are downleveled.
```typescript
export const DIRECTIVES = [
FooDir,
BarDir,
];
```
can be downleveled to:
```javascript
exports.DIRECTIVES = [
FooDir,
BarDir,
```
Previously, ngtsc and ngcc expected that any export would have an associated
`ts.Declaration` node. `export class`, `export function`, etc. all retain
`ts.Declaration`s even when downleveled. But the `export const` construct
above does not. Therefore, ngcc would not detect `DIRECTIVES` as an export
of `foo.ts`, and the evaluation of `foo.DIRECTIVES` would therefore fail.
To solve this problem, the core concept of an exported `Declaration`
according to the `ReflectionHost` API is split into a `ConcreteDeclaration`
which has a `ts.Declaration`, and an `InlineDeclaration` which instead has
a `ts.Expression`. Differentiating between these allows ngcc to return an
`InlineDeclaration` for `DIRECTIVES` and correctly keep track of this
export.
PR Close #32129
2019-08-13 16:08:53 -07:00
|
|
|
if (declaration.node !== null && hasNameIdentifier(declaration.node)) {
|
2019-05-14 08:36:57 +01:00
|
|
|
if (privateDeclarations.has(declaration.node.name)) {
|
2020-04-06 08:30:08 +01:00
|
|
|
const privateDeclaration = privateDeclarations.get(declaration.node.name)!;
|
2019-02-14 18:59:46 +01:00
|
|
|
if (privateDeclaration.node !== declaration.node) {
|
|
|
|
|
throw new Error(`${declaration.node.name.text} is declared multiple times.`);
|
|
|
|
|
}
|
2019-12-18 14:03:05 +00:00
|
|
|
// This declaration is public so we can remove it from the list
|
|
|
|
|
privateDeclarations.delete(declaration.node.name);
|
2019-02-14 18:59:46 +01:00
|
|
|
}
|
2018-11-21 09:05:27 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-02-14 18:59:46 +01:00
|
|
|
|
2018-11-21 09:05:27 +00:00
|
|
|
return Array.from(privateDeclarations.keys()).map(id => {
|
2019-06-06 20:22:32 +01:00
|
|
|
const from = absoluteFromSourceFile(id.getSourceFile());
|
2020-04-06 08:30:08 +01:00
|
|
|
const declaration = privateDeclarations.get(id)!;
|
2018-11-29 08:26:00 +00:00
|
|
|
const dtsDeclaration = this.host.getDtsDeclaration(declaration.node);
|
2019-06-06 20:22:32 +01:00
|
|
|
const dtsFrom = dtsDeclaration && absoluteFromSourceFile(dtsDeclaration.getSourceFile());
|
2019-02-14 18:59:46 +01:00
|
|
|
|
2019-12-18 14:03:05 +00:00
|
|
|
return {identifier: id.text, from, dtsFrom};
|
2018-11-21 09:05:27 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|