feat(ivy): implement `Esm5ReflectionHost.getGenericArityOfClass()` (#25406)
PR Close #25406
This commit is contained in:
parent
6ae1e63c89
commit
7ce291c72a
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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 {relative, resolve} from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map source files to their associated typings definitions files.
|
||||||
|
*/
|
||||||
|
export class DtsMapper {
|
||||||
|
constructor(private sourceRoot: string, private dtsRoot: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the absolute path to a source file, return the absolute path to the corresponding `.d.ts`
|
||||||
|
* file. Assume that source files and `.d.ts` files have the same directory layout and the names
|
||||||
|
* of the `.d.ts` files can be derived by replacing the `.js` extension of the source file with
|
||||||
|
* `.d.ts`.
|
||||||
|
*
|
||||||
|
* @param sourceFileName The absolute path to the source file whose corresponding `.d.ts` file
|
||||||
|
* should be returned.
|
||||||
|
*
|
||||||
|
* @returns The absolute path to the `.d.ts` file that corresponds to the specified source file.
|
||||||
|
*/
|
||||||
|
getDtsFileNameFor(sourceFileName: string): string {
|
||||||
|
const relativeSourcePath = relative(this.sourceRoot, sourceFileName);
|
||||||
|
return resolve(this.dtsRoot, relativeSourcePath).replace(/\.js$/, '.d.ts');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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 {readFileSync} from 'fs';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {DtsMapper} from './dts_mapper';
|
||||||
|
import {Fesm2015ReflectionHost} from './fesm2015_host';
|
||||||
|
|
||||||
|
export class Esm2015ReflectionHost extends Fesm2015ReflectionHost {
|
||||||
|
constructor(checker: ts.TypeChecker, protected dtsMapper: DtsMapper) { super(checker); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of generic type parameters of a given class.
|
||||||
|
*
|
||||||
|
* @returns the number of type parameters of the class, if known, or `null` if the declaration
|
||||||
|
* is not a class or has an unknown number of type parameters.
|
||||||
|
*/
|
||||||
|
getGenericArityOfClass(clazz: ts.Declaration): number|null {
|
||||||
|
if (ts.isClassDeclaration(clazz) && clazz.name) {
|
||||||
|
const sourcePath = clazz.getSourceFile();
|
||||||
|
const dtsPath = this.dtsMapper.getDtsFileNameFor(sourcePath.fileName);
|
||||||
|
const dtsContents = readFileSync(dtsPath, 'utf8');
|
||||||
|
// TODO: investigate caching parsed .d.ts files as they're needed for several different
|
||||||
|
// purposes in ngcc.
|
||||||
|
const dtsFile = ts.createSourceFile(
|
||||||
|
dtsPath, dtsContents, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
||||||
|
|
||||||
|
for (let i = dtsFile.statements.length - 1; i >= 0; i--) {
|
||||||
|
const stmt = dtsFile.statements[i];
|
||||||
|
if (ts.isClassDeclaration(stmt) && stmt.name !== undefined &&
|
||||||
|
stmt.name.text === clazz.name.text) {
|
||||||
|
return stmt.typeParameters ? stmt.typeParameters.length : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,20 +11,23 @@ import {mkdir} from 'shelljs';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {DtsFileTransformer} from '../../../ngtsc/transform';
|
import {DtsFileTransformer} from '../../../ngtsc/transform';
|
||||||
|
|
||||||
import {AnalyzedFile, Analyzer} from '../analyzer';
|
import {AnalyzedFile, Analyzer} from '../analyzer';
|
||||||
import {IMPORT_PREFIX} from '../constants';
|
import {IMPORT_PREFIX} from '../constants';
|
||||||
|
import {DtsMapper} from '../host/dts_mapper';
|
||||||
|
import {Esm2015ReflectionHost} from '../host/esm2015_host';
|
||||||
import {Esm5ReflectionHost} from '../host/esm5_host';
|
import {Esm5ReflectionHost} from '../host/esm5_host';
|
||||||
import {Fesm2015ReflectionHost} from '../host/fesm2015_host';
|
import {Fesm2015ReflectionHost} from '../host/fesm2015_host';
|
||||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
import {Esm2015FileParser} from '../parsing/esm2015_parser';
|
import {Esm2015FileParser} from '../parsing/esm2015_parser';
|
||||||
import {Esm5FileParser} from '../parsing/esm5_parser';
|
import {Esm5FileParser} from '../parsing/esm5_parser';
|
||||||
import {FileParser} from '../parsing/file_parser';
|
import {FileParser} from '../parsing/file_parser';
|
||||||
import {EntryPoint, getEntryPoints} from '../parsing/utils';
|
|
||||||
import {Esm2015Renderer} from '../rendering/esm2015_renderer';
|
import {Esm2015Renderer} from '../rendering/esm2015_renderer';
|
||||||
import {Esm5Renderer} from '../rendering/esm5_renderer';
|
import {Esm5Renderer} from '../rendering/esm5_renderer';
|
||||||
import {FileInfo, Renderer} from '../rendering/renderer';
|
import {FileInfo, Renderer} from '../rendering/renderer';
|
||||||
|
|
||||||
|
import {getEntryPoints} from './utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Package is stored in a directory on disk and that directory can contain one or more package
|
* A Package is stored in a directory on disk and that directory can contain one or more package
|
||||||
|
@ -63,7 +66,8 @@ export class PackageTransformer {
|
||||||
const host = ts.createCompilerHost(options);
|
const host = ts.createCompilerHost(options);
|
||||||
const packageProgram = ts.createProgram([entryPoint.entryFileName], options, host);
|
const packageProgram = ts.createProgram([entryPoint.entryFileName], options, host);
|
||||||
const typeChecker = packageProgram.getTypeChecker();
|
const typeChecker = packageProgram.getTypeChecker();
|
||||||
const reflectionHost = this.getHost(format, packageProgram);
|
const dtsMapper = new DtsMapper(entryPoint.entryRoot, entryPoint.dtsEntryRoot);
|
||||||
|
const reflectionHost = this.getHost(format, packageProgram, dtsMapper);
|
||||||
|
|
||||||
const parser = this.getFileParser(format, packageProgram, reflectionHost);
|
const parser = this.getFileParser(format, packageProgram, reflectionHost);
|
||||||
const analyzer = new Analyzer(typeChecker, reflectionHost);
|
const analyzer = new Analyzer(typeChecker, reflectionHost);
|
||||||
|
@ -83,7 +87,7 @@ export class PackageTransformer {
|
||||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
|
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
|
||||||
if (format === 'esm2015') {
|
if (format === 'esm2015') {
|
||||||
outputFiles.push(...this.transformDtsFiles(
|
outputFiles.push(...this.transformDtsFiles(
|
||||||
analyzedFiles, sourceNodeModules, targetNodeModules, entryPoint));
|
analyzedFiles, sourceNodeModules, targetNodeModules, dtsMapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write out all the transformed files.
|
// Write out all the transformed files.
|
||||||
|
@ -91,9 +95,10 @@ export class PackageTransformer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getHost(format: string, program: ts.Program): NgccReflectionHost {
|
getHost(format: string, program: ts.Program, dtsMapper: DtsMapper): NgccReflectionHost {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'esm2015':
|
case 'esm2015':
|
||||||
|
return new Esm2015ReflectionHost(program.getTypeChecker(), dtsMapper);
|
||||||
case 'fesm2015':
|
case 'fesm2015':
|
||||||
return new Fesm2015ReflectionHost(program.getTypeChecker());
|
return new Fesm2015ReflectionHost(program.getTypeChecker());
|
||||||
case 'esm5':
|
case 'esm5':
|
||||||
|
@ -139,7 +144,7 @@ export class PackageTransformer {
|
||||||
|
|
||||||
transformDtsFiles(
|
transformDtsFiles(
|
||||||
analyzedFiles: AnalyzedFile[], sourceNodeModules: string, targetNodeModules: string,
|
analyzedFiles: AnalyzedFile[], sourceNodeModules: string, targetNodeModules: string,
|
||||||
entryPoint: EntryPoint): FileInfo[] {
|
dtsMapper: DtsMapper): FileInfo[] {
|
||||||
const outputFiles: FileInfo[] = [];
|
const outputFiles: FileInfo[] = [];
|
||||||
|
|
||||||
analyzedFiles.forEach(analyzedFile => {
|
analyzedFiles.forEach(analyzedFile => {
|
||||||
|
@ -152,7 +157,7 @@ export class PackageTransformer {
|
||||||
|
|
||||||
// Find the corresponding `.d.ts` file.
|
// Find the corresponding `.d.ts` file.
|
||||||
const sourceFileName = analyzedFile.sourceFile.fileName;
|
const sourceFileName = analyzedFile.sourceFile.fileName;
|
||||||
const originalDtsFileName = entryPoint.getDtsFileNameFor(sourceFileName);
|
const originalDtsFileName = dtsMapper.getDtsFileNameFor(sourceFileName);
|
||||||
const originalDtsContents = readFileSync(originalDtsFileName, 'utf8');
|
const originalDtsContents = readFileSync(originalDtsFileName, 'utf8');
|
||||||
|
|
||||||
// Transform the `.d.ts` file based on the recorded source file changes.
|
// Transform the `.d.ts` file based on the recorded source file changes.
|
||||||
|
|
|
@ -21,45 +21,20 @@ import {isDefined} from '../utils';
|
||||||
*/
|
*/
|
||||||
export class EntryPoint {
|
export class EntryPoint {
|
||||||
entryFileName: string;
|
entryFileName: string;
|
||||||
private entryRoot: string;
|
entryRoot: string;
|
||||||
private dtsEntryRoot?: string;
|
dtsEntryRoot: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param packageRoot The absolute path to the root directory that contains the package.
|
* @param packageRoot The absolute path to the root directory that contains the package.
|
||||||
* @param relativeEntryPath The relative path to the entry point file.
|
* @param relativeEntryPath The relative path to the entry point file.
|
||||||
* @param relativeDtsEntryPath The relative path to the `.d.ts` entry point file.
|
* @param relativeDtsEntryPath The relative path to the `.d.ts` entry point file.
|
||||||
*/
|
*/
|
||||||
constructor(packageRoot: string, relativeEntryPath: string, relativeDtsEntryPath?: string) {
|
constructor(packageRoot: string, relativeEntryPath: string, relativeDtsEntryPath: string) {
|
||||||
this.entryFileName = resolve(packageRoot, relativeEntryPath);
|
this.entryFileName = resolve(packageRoot, relativeEntryPath);
|
||||||
this.entryRoot = dirname(this.entryFileName);
|
this.entryRoot = dirname(this.entryFileName);
|
||||||
|
|
||||||
if (isDefined(relativeDtsEntryPath)) {
|
|
||||||
const dtsEntryFileName = resolve(packageRoot, relativeDtsEntryPath);
|
const dtsEntryFileName = resolve(packageRoot, relativeDtsEntryPath);
|
||||||
this.dtsEntryRoot = dirname(dtsEntryFileName);
|
this.dtsEntryRoot = dirname(dtsEntryFileName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the absolute path to a source file, return the absolute path to the corresponding `.d.ts`
|
|
||||||
* file. Assume that source files and `.d.ts` files have the same directory layout and the names
|
|
||||||
* of the `.d.ts` files can be derived by replacing the `.js` extension of the source file with
|
|
||||||
* `.d.ts`.
|
|
||||||
*
|
|
||||||
* @param sourceFileName The absolute path to the source file whose corresponding `.d.ts` file
|
|
||||||
* should be returned.
|
|
||||||
*
|
|
||||||
* @returns The absolute path to the `.d.ts` file that corresponds to the specified source file.
|
|
||||||
*/
|
|
||||||
getDtsFileNameFor(sourceFileName: string): string {
|
|
||||||
if (!isDefined(this.dtsEntryRoot)) {
|
|
||||||
throw new Error('No `.d.ts` entry path was specified.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativeSourcePath = relative(this.entryRoot, sourceFileName);
|
|
||||||
const dtsFileName = resolve(this.dtsEntryRoot, relativeSourcePath).replace(/\.js$/, '.d.ts');
|
|
||||||
|
|
||||||
return dtsFileName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,14 +72,17 @@ export function findAllPackageJsonFiles(rootDirectory: string): string[] {
|
||||||
*/
|
*/
|
||||||
export function getEntryPoints(packageDirectory: string, format: string): EntryPoint[] {
|
export function getEntryPoints(packageDirectory: string, format: string): EntryPoint[] {
|
||||||
const packageJsonPaths = findAllPackageJsonFiles(packageDirectory);
|
const packageJsonPaths = findAllPackageJsonFiles(packageDirectory);
|
||||||
return packageJsonPaths
|
const entryPoints =
|
||||||
|
packageJsonPaths
|
||||||
.map(packageJsonPath => {
|
.map(packageJsonPath => {
|
||||||
const entryPointPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
const entryPointPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||||
const relativeEntryPointPath = entryPointPackageJson[format];
|
const entryPointPath: string|undefined = entryPointPackageJson[format];
|
||||||
const relativeDtsEntryPointPath = entryPointPackageJson.typings;
|
if (!entryPointPath) {
|
||||||
return relativeEntryPointPath &&
|
return undefined;
|
||||||
new EntryPoint(
|
}
|
||||||
dirname(packageJsonPath), relativeEntryPointPath, relativeDtsEntryPointPath);
|
const dtsEntryPointPath = entryPointPackageJson.typings || entryPointPath;
|
||||||
|
return new EntryPoint(dirname(packageJsonPath), entryPointPath, dtsEntryPointPath);
|
||||||
})
|
})
|
||||||
.filter(entryPoint => entryPoint);
|
.filter(isDefined);
|
||||||
|
return entryPoints;
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassMemberKind, Import} from '../../../ngtsc/host';
|
import {ClassMemberKind, Import} from '../../../ngtsc/host';
|
||||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||||
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
|
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as mockFs from 'mock-fs';
|
import * as mockFs from 'mock-fs';
|
||||||
import {EntryPoint, findAllPackageJsonFiles, getEntryPoints} from '../../src/parsing/utils';
|
import {EntryPoint, findAllPackageJsonFiles, getEntryPoints} from '../../src/transform/utils';
|
||||||
|
|
||||||
function createMockFileSystem() {
|
function createMockFileSystem() {
|
||||||
mockFs({
|
mockFs({
|
||||||
|
@ -33,7 +33,7 @@ function createMockFileSystem() {
|
||||||
'package.json': `{
|
'package.json': `{
|
||||||
"fesm2015": "../../fesm2015/http/testing.js",
|
"fesm2015": "../../fesm2015/http/testing.js",
|
||||||
"fesm5": "../../fesm5/http/testing.js",
|
"fesm5": "../../fesm5/http/testing.js",
|
||||||
"no-typings": "for testing purposes"
|
"typings": "../http/testing.d.ts"
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,7 @@ function createMockFileSystem() {
|
||||||
'package.json': `{
|
'package.json': `{
|
||||||
"fesm2015": "../fesm2015/testing.js",
|
"fesm2015": "../fesm2015/testing.js",
|
||||||
"fesm5": "../fesm5/testing.js",
|
"fesm5": "../fesm5/testing.js",
|
||||||
"no-typings": "for testing purposes"
|
"typings": "../testing.d.ts"
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
'node_modules': {
|
'node_modules': {
|
||||||
|
@ -58,6 +58,15 @@ function createMockFileSystem() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'/node_modules/@angular/no-typings': {
|
||||||
|
'package.json': `{
|
||||||
|
"fesm2015": "./fesm2015/index.js"
|
||||||
|
}`,
|
||||||
|
'fesm2015': {
|
||||||
|
'index.js': 'DUMMY CONTENT',
|
||||||
|
'index.d.ts': 'DUMMY CONTENT',
|
||||||
|
},
|
||||||
|
},
|
||||||
'/node_modules/@angular/other': {
|
'/node_modules/@angular/other': {
|
||||||
'not-package.json': '{ "fesm2015": "./fesm2015/other.js" }',
|
'not-package.json': '{ "fesm2015": "./fesm2015/other.js" }',
|
||||||
'package.jsonot': '{ "fesm5": "./fesm5/other.js" }',
|
'package.jsonot': '{ "fesm5": "./fesm5/other.js" }',
|
||||||
|
@ -82,26 +91,10 @@ function restoreRealFileSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('EntryPoint', () => {
|
describe('EntryPoint', () => {
|
||||||
it('should not break when called without a `relativeDtsEntryPath`',
|
|
||||||
() => { expect(() => new EntryPoint('/foo', './bar')).not.toThrow(); });
|
|
||||||
|
|
||||||
it('should expose the absolute path to the entry point file', () => {
|
it('should expose the absolute path to the entry point file', () => {
|
||||||
const entryPoint = new EntryPoint('/foo/bar', '../baz/qux/../quux.js');
|
const entryPoint = new EntryPoint('/foo/bar', '../baz/qux/../quux.js', '/typings/foo/bar.d.ts');
|
||||||
expect(entryPoint.entryFileName).toBe('/foo/baz/quux.js');
|
expect(entryPoint.entryFileName).toBe('/foo/baz/quux.js');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.getDtsFileNameFor()', () => {
|
|
||||||
it('should throw if no `.d.ts` entry path was specified', () => {
|
|
||||||
const entryPoint = new EntryPoint('/foo/bar', '../baz/qux.js');
|
|
||||||
expect(() => entryPoint.getDtsFileNameFor('test'))
|
|
||||||
.toThrowError('No `.d.ts` entry path was specified.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the absolute path to the corresponding `.d.ts` file', () => {
|
|
||||||
const entryPoint = new EntryPoint('/foo/bar', '../src/entry.js', '../dts/entry.d.ts');
|
|
||||||
expect(entryPoint.getDtsFileNameFor('/foo/src/qu/x.js')).toBe('/foo/dts/qu/x.d.ts');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findAllPackageJsonFiles()', () => {
|
describe('findAllPackageJsonFiles()', () => {
|
||||||
|
@ -168,19 +161,11 @@ describe('getEntryPoints()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an entry point even if the typings are not specified', () => {
|
it('should return an entry point even if the typings are not specified', () => {
|
||||||
const entryPoints = getEntryPoints('/node_modules/@angular/common/http', 'fesm2015');
|
const entryPoints = getEntryPoints('/node_modules/@angular/no-typings', 'fesm2015');
|
||||||
const sortedEntryPoints =
|
expect(entryPoints.length).toEqual(1);
|
||||||
entryPoints.sort((a, b) => (a.entryFileName > b.entryFileName) ? 1 : -1);
|
expect(entryPoints[0].entryFileName)
|
||||||
const sortedPaths = sortedEntryPoints.map(x => x.entryFileName);
|
.toEqual('/node_modules/@angular/no-typings/fesm2015/index.js');
|
||||||
|
expect(entryPoints[0].entryRoot).toEqual('/node_modules/@angular/no-typings/fesm2015');
|
||||||
expect(sortedPaths).toEqual([
|
expect(entryPoints[0].dtsEntryRoot).toEqual(entryPoints[0].entryRoot);
|
||||||
'/node_modules/@angular/common/fesm2015/http.js',
|
|
||||||
'/node_modules/@angular/common/fesm2015/http/testing.js',
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(() => sortedEntryPoints[0].getDtsFileNameFor(sortedEntryPoints[0].entryFileName))
|
|
||||||
.not.toThrow();
|
|
||||||
expect(() => sortedEntryPoints[1].getDtsFileNameFor(sortedEntryPoints[1].entryFileName))
|
|
||||||
.toThrow();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue