Skip to content

Commit dddbb1c

Browse files
tboschchuckjaz
authored andcommitted
refactor(tsc-wrapped): collect all exported functions and classes and bump metadata version from 1 to 2
This is needed to resolve symbols without `.d.ts` files. This bumps the version of the metadata from 1 to 2. This adds logic into `ng_host.ts` to automatically upgrade version 1 to version 2 metadata by adding the exported symbols from the `.d.ts` file.
1 parent bccf0e6 commit dddbb1c

File tree

9 files changed

+169
-92
lines changed

9 files changed

+169
-92
lines changed

modules/@angular/compiler-cli/src/ng_host.ts

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export class NgHost implements AotCompilerHost {
3131
private isGenDirChildOfRootDir: boolean;
3232
protected basePath: string;
3333
private genDir: string;
34+
private resolverCache = new Map<string, ModuleMetadata[]>();
35+
3436
constructor(
3537
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
3638
protected options: AngularCompilerOptions, context?: NgHostContext) {
@@ -138,9 +140,18 @@ export class NgHost implements AotCompilerHost {
138140
}
139141
}
140142

141-
private resolverCache = new Map<string, ModuleMetadata>();
143+
protected getSourceFile(filePath: string): ts.SourceFile {
144+
const sf = this.program.getSourceFile(filePath);
145+
if (!sf) {
146+
if (this.context.fileExists(filePath)) {
147+
const sourceText = this.context.readFile(filePath);
148+
return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
149+
}
150+
throw new Error(`Source file ${filePath} not present in program.`);
151+
}
152+
}
142153

143-
getMetadataFor(filePath: string): ModuleMetadata {
154+
getMetadataFor(filePath: string): ModuleMetadata[] {
144155
if (!this.context.fileExists(filePath)) {
145156
// If the file doesn't exists then we cannot return metadata for the file.
146157
// This will occur if the user refernced a declared module for which no file
@@ -150,43 +161,58 @@ export class NgHost implements AotCompilerHost {
150161
if (DTS.test(filePath)) {
151162
const metadataPath = filePath.replace(DTS, '.metadata.json');
152163
if (this.context.fileExists(metadataPath)) {
153-
const metadata = this.readMetadata(metadataPath);
154-
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
164+
return this.readMetadata(metadataPath, filePath);
155165
}
156166
} else {
157-
const sf = this.program.getSourceFile(filePath);
158-
if (!sf) {
159-
if (this.context.fileExists(filePath)) {
160-
const sourceText = this.context.readFile(filePath);
161-
return this.metadataCollector.getMetadata(
162-
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
163-
}
164-
165-
throw new Error(`Source file ${filePath} not present in program.`);
166-
}
167-
return this.metadataCollector.getMetadata(sf);
167+
const sf = this.getSourceFile(filePath);
168+
const metadata = this.metadataCollector.getMetadata(sf);
169+
return metadata ? [metadata] : [];
168170
}
169171
}
170172

171-
readMetadata(filePath: string) {
173+
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] {
174+
let metadatas = this.resolverCache.get(filePath);
175+
if (metadatas) {
176+
return metadatas;
177+
}
172178
try {
173-
return this.resolverCache.get(filePath) || JSON.parse(this.context.readFile(filePath));
179+
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
180+
const metadatas = metadataOrMetadatas ?
181+
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
182+
[];
183+
const v1Metadata = metadatas.find(m => m['version'] === 1);
184+
let v2Metadata = metadatas.find(m => m['version'] === 2);
185+
if (!v2Metadata && v1Metadata) {
186+
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
187+
// as the only difference between the versions is whether all exports are contained in
188+
// the metadata
189+
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
190+
if (v1Metadata.exports) {
191+
v2Metadata.exports = v1Metadata.exports;
192+
}
193+
for (let prop in v1Metadata.metadata) {
194+
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
195+
}
196+
const sourceText = this.context.readFile(dtsFilePath);
197+
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
198+
if (exports) {
199+
for (let prop in exports.metadata) {
200+
if (!v2Metadata.metadata[prop]) {
201+
v2Metadata.metadata[prop] = exports.metadata[prop];
202+
}
203+
}
204+
}
205+
metadatas.push(v2Metadata);
206+
}
207+
this.resolverCache.set(filePath, metadatas);
208+
return metadatas;
174209
} catch (e) {
175210
console.error(`Failed to read JSON file ${filePath}`);
176211
throw e;
177212
}
178213
}
179214

180215
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
181-
182-
private getResolverMetadata(filePath: string): ModuleMetadata {
183-
let metadata = this.resolverCache.get(filePath);
184-
if (!metadata) {
185-
metadata = this.getMetadataFor(filePath);
186-
this.resolverCache.set(filePath, metadata);
187-
}
188-
return metadata;
189-
}
190216
}
191217

192218
export class NodeNgHostContext implements NgHostContext {

modules/@angular/compiler-cli/src/path_mapped_ng_host.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class PathMappedNgHost extends NgHost {
116116
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
117117
}
118118

119-
getMetadataFor(filePath: string): ModuleMetadata {
119+
getMetadataFor(filePath: string): ModuleMetadata[] {
120120
for (const root of this.options.rootDirs || []) {
121121
const rootedPath = path.join(root, filePath);
122122
if (!this.compilerHost.fileExists(rootedPath)) {
@@ -128,16 +128,13 @@ export class PathMappedNgHost extends NgHost {
128128
if (DTS.test(rootedPath)) {
129129
const metadataPath = rootedPath.replace(DTS, '.metadata.json');
130130
if (this.context.fileExists(metadataPath)) {
131-
const metadata = this.readMetadata(metadataPath);
132-
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
131+
return this.readMetadata(metadataPath, rootedPath);
133132
}
134133
} else {
135-
const sf = this.program.getSourceFile(rootedPath);
136-
if (!sf) {
137-
throw new Error(`Source file ${rootedPath} not present in program.`);
138-
}
134+
const sf = this.getSourceFile(rootedPath);
139135
sf.fileName = this.getCanonicalFileName(sf.fileName);
140-
return this.metadataCollector.getMetadata(sf);
136+
const metadata = this.metadataCollector.getMetadata(sf);
137+
return metadata ? [metadata] : [];
141138
}
142139
}
143140
}

modules/@angular/compiler-cli/test/ng_host_spec.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,32 @@ describe('NgHost', () => {
141141
});
142142

143143
it('should be able to read a metadata file', () => {
144-
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts'))
145-
.toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}});
144+
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
145+
{__symbolic: 'module', version: 2, metadata: {foo: {__symbolic: 'class'}}}
146+
]);
146147
});
147148

148149
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
149150
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
150151
});
151152

152153
it('should be able to read empty metadata ', () => {
153-
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toBeUndefined();
154+
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
154155
});
155156

156157
it('should return undefined for missing modules', () => {
157158
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
158159
});
160+
161+
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
162+
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
163+
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
164+
__symbolic: 'module',
165+
version: 2,
166+
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
167+
}
168+
]);
169+
});
159170
});
160171

161172
const dummyModule = 'export let foo: any[];';
@@ -179,12 +190,17 @@ const FILES: Entry = {
179190
'@angular': {
180191
'core.d.ts': dummyModule,
181192
'core.metadata.json':
182-
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
193+
`{"__symbolic":"module", "version": 2, "metadata": {"foo": {"__symbolic": "class"}}}`,
183194
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
184195
'unused.d.ts': dummyModule,
185196
'empty.d.ts': 'export declare var a: string;',
186197
'empty.metadata.json': '[]',
187198
}
199+
},
200+
'metadata_versions': {
201+
'v1.d.ts': 'export declare class bar {}',
202+
'v1.metadata.json':
203+
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
188204
}
189205
}
190206
}

modules/@angular/compiler/src/aot/compiler_host.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface AotCompilerHost {
2222
* @param modulePath is a string identifier for a module as an absolute path.
2323
* @returns the metadata for the given module.
2424
*/
25-
getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[];
25+
getMetadataFor(modulePath: string): {[key: string]: any}[];
2626

2727
/**
2828
* Converts an import into a file path.

modules/@angular/compiler/src/aot/static_reflector.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {ReflectorReader} from '../private_import_core';
1111
import {AotCompilerHost} from './compiler_host';
1212
import {StaticSymbol} from './static_symbol';
1313

14-
const SUPPORTED_SCHEMA_VERSION = 1;
14+
const SUPPORTED_SCHEMA_VERSION = 2;
1515
const ANGULAR_IMPORT_LOCATIONS = {
1616
coreDecorators: '@angular/core/src/metadata',
1717
diDecorators: '@angular/core/src/di/metadata',
@@ -604,10 +604,15 @@ export class StaticReflector implements ReflectorReader {
604604
public getModuleMetadata(module: string): {[key: string]: any} {
605605
let moduleMetadata = this.metadataCache.get(module);
606606
if (!moduleMetadata) {
607-
moduleMetadata = this.host.getMetadataFor(module);
608-
if (Array.isArray(moduleMetadata)) {
609-
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
610-
moduleMetadata[0];
607+
const moduleMetadatas = this.host.getMetadataFor(module);
608+
if (moduleMetadatas) {
609+
let maxVersion = -1;
610+
moduleMetadatas.forEach((md) => {
611+
if (md['version'] > maxVersion) {
612+
maxVersion = md['version'];
613+
moduleMetadata = md;
614+
}
615+
});
611616
}
612617
if (!moduleMetadata) {
613618
moduleMetadata =
@@ -653,6 +658,7 @@ function expandedMessage(error: any): string {
653658
if (error.context && error.context.name) {
654659
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
655660
}
661+
break;
656662
}
657663
return error.message;
658664
}

0 commit comments

Comments
 (0)