fix(ivy): ngcc - recognize suffixed tslib helpers (#31614)
An identifier may become repeated when bundling multiple source files into a single bundle, so bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. Since ngcc operates on such bundles, it needs to process potentially suffixed identifiers in their canonical form without the suffix. The "ngx-pagination" package was previously not compiled fully, as most decorators were not recognized. This commit ensures that identifiers are first canonicalized by removing the suffix, such that they are properly recognized and processed by ngcc. Fixes #31540 PR Close #31614
This commit is contained in:
parent
5e5be43acd
commit
80f290e301
|
@ -12,7 +12,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||||
import {Logger} from '../logging/logger';
|
import {Logger} from '../logging/logger';
|
||||||
import {BundleProgram} from '../packages/bundle_program';
|
import {BundleProgram} from '../packages/bundle_program';
|
||||||
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
|
import {findAll, getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils';
|
||||||
|
|
||||||
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||||
|
|
||||||
|
@ -889,14 +889,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
}
|
}
|
||||||
const call = expression;
|
const call = expression;
|
||||||
|
|
||||||
const helperCallFn =
|
const helperName = getCalleeName(call);
|
||||||
ts.isPropertyAccessExpression(call.expression) ? call.expression.name : call.expression;
|
if (helperName === '__metadata') {
|
||||||
if (!ts.isIdentifier(helperCallFn)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = helperCallFn.text;
|
|
||||||
if (name === '__metadata') {
|
|
||||||
// This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object
|
// This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object
|
||||||
// if the metadata key is "design:paramtypes".
|
// if the metadata key is "design:paramtypes".
|
||||||
const key = call.arguments[0];
|
const key = call.arguments[0];
|
||||||
|
@ -915,7 +909,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === '__param') {
|
if (helperName === '__param') {
|
||||||
// This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object.
|
// This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object.
|
||||||
const indexArg = call.arguments[0];
|
const indexArg = call.arguments[0];
|
||||||
const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN;
|
const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN;
|
||||||
|
@ -1613,10 +1607,10 @@ export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression
|
||||||
*/
|
*/
|
||||||
function getCalleeName(call: ts.CallExpression): string|null {
|
function getCalleeName(call: ts.CallExpression): string|null {
|
||||||
if (ts.isIdentifier(call.expression)) {
|
if (ts.isIdentifier(call.expression)) {
|
||||||
return call.expression.text;
|
return stripDollarSuffix(call.expression.text);
|
||||||
}
|
}
|
||||||
if (ts.isPropertyAccessExpression(call.expression)) {
|
if (ts.isPropertyAccessExpression(call.expression)) {
|
||||||
return call.expression.name.text;
|
return stripDollarSuffix(call.expression.name.text);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||||
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
|
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
|
||||||
import {getNameText, hasNameIdentifier} from '../utils';
|
import {getNameText, hasNameIdentifier, stripDollarSuffix} from '../utils';
|
||||||
|
|
||||||
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||||
|
|
||||||
|
@ -659,7 +659,9 @@ function reflectArrayElement(element: ts.Expression) {
|
||||||
* helper.
|
* helper.
|
||||||
*/
|
*/
|
||||||
function getTsHelperFn(node: ts.NamedDeclaration): TsHelperFn|null {
|
function getTsHelperFn(node: ts.NamedDeclaration): TsHelperFn|null {
|
||||||
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
|
const name = node.name !== undefined && ts.isIdentifier(node.name) ?
|
||||||
|
stripDollarSuffix(node.name.text) :
|
||||||
|
null;
|
||||||
|
|
||||||
if (name === '__spread') {
|
if (name === '__spread') {
|
||||||
return TsHelperFn.Spread;
|
return TsHelperFn.Spread;
|
||||||
|
|
|
@ -82,3 +82,14 @@ export function resolveFileWithPostfixes(
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier may become repeated when bundling multiple source files into a single bundle, so
|
||||||
|
* bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. This function
|
||||||
|
* strips off such suffixes, so that ngcc deals with the canonical name of an identifier.
|
||||||
|
* @param value The value to strip any suffix of, if applicable.
|
||||||
|
* @returns The canonical representation of the value, without any suffix.
|
||||||
|
*/
|
||||||
|
export function stripDollarSuffix(value: string): string {
|
||||||
|
return value.replace(/\$\d+$/, '');
|
||||||
|
}
|
||||||
|
|
|
@ -78,18 +78,18 @@ export function convertToDirectTsLibImport(filesystem: TestFile[]) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertToInlineTsLib(filesystem: TestFile[]) {
|
export function convertToInlineTsLib(filesystem: TestFile[], suffix: string = '') {
|
||||||
return filesystem.map(file => {
|
return filesystem.map(file => {
|
||||||
const contents = file.contents
|
const contents = file.contents
|
||||||
.replace(`import * as tslib_1 from 'tslib';`, `
|
.replace(`import * as tslib_1 from 'tslib';`, `
|
||||||
var __decorate = null;
|
var __decorate${suffix} = null;
|
||||||
var __metadata = null;
|
var __metadata${suffix} = null;
|
||||||
var __read = null;
|
var __read${suffix} = null;
|
||||||
var __values = null;
|
var __values${suffix} = null;
|
||||||
var __param = null;
|
var __param${suffix} = null;
|
||||||
var __extends = null;
|
var __extends${suffix} = null;
|
||||||
var __assign = null;
|
var __assign${suffix} = null;
|
||||||
`).replace(/tslib_1\./g, '');
|
`).replace(/tslib_1\.([_a-z]+)/gi, '$1' + suffix.replace('$', '$$'));
|
||||||
return {...file, contents};
|
return {...file, contents};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,15 +112,17 @@ runInEachFileSystem(() => {
|
||||||
|
|
||||||
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
||||||
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
|
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
|
||||||
|
const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2');
|
||||||
|
|
||||||
FILES = {
|
FILES = {
|
||||||
'namespaced': NAMESPACED_IMPORT_FILES,
|
'namespaced': NAMESPACED_IMPORT_FILES,
|
||||||
'direct import': DIRECT_IMPORT_FILES,
|
'direct import': DIRECT_IMPORT_FILES,
|
||||||
'inline': INLINE_FILES,
|
'inline': INLINE_FILES,
|
||||||
|
'inline suffixed': INLINE_SUFFIXED_FILES,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
['namespaced', 'direct import', 'inline'].forEach(label => {
|
['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => {
|
||||||
describe(`[${label}]`, () => {
|
describe(`[${label}]`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
|
|
|
@ -133,15 +133,17 @@ export { SomeDirective };
|
||||||
|
|
||||||
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
||||||
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
|
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
|
||||||
|
const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2');
|
||||||
|
|
||||||
FILES = {
|
FILES = {
|
||||||
'namespaced': NAMESPACED_IMPORT_FILES,
|
'namespaced': NAMESPACED_IMPORT_FILES,
|
||||||
'direct import': DIRECT_IMPORT_FILES,
|
'direct import': DIRECT_IMPORT_FILES,
|
||||||
'inline': INLINE_FILES,
|
'inline': INLINE_FILES,
|
||||||
|
'inline suffixed': INLINE_SUFFIXED_FILES,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
['namespaced', 'direct import', 'inline'].forEach(label => {
|
['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => {
|
||||||
describe(`[${label}]`, () => {
|
describe(`[${label}]`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
|
|
|
@ -1669,6 +1669,30 @@ runInEachFileSystem(() => {
|
||||||
expect(definition.helper).toBe(TsHelperFn.Spread);
|
expect(definition.helper).toBe(TsHelperFn.Spread);
|
||||||
expect(definition.parameters.length).toEqual(0);
|
expect(definition.parameters.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should recognize TypeScript __spread helper function implementation when suffixed',
|
||||||
|
() => {
|
||||||
|
const file: TestFile = {
|
||||||
|
name: _('/implementation.js'),
|
||||||
|
contents: `
|
||||||
|
var __spread$2 = (this && this.__spread$2) || function () {
|
||||||
|
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
|
||||||
|
return ar;
|
||||||
|
};`,
|
||||||
|
};
|
||||||
|
loadTestFiles([file]);
|
||||||
|
const {program} = makeTestBundleProgram(file.name);
|
||||||
|
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
|
||||||
|
const node =
|
||||||
|
getDeclaration(program, file.name, '__spread$2', ts.isVariableDeclaration) !;
|
||||||
|
|
||||||
|
const definition = host.getDefinitionOfFunction(node) !;
|
||||||
|
expect(definition.node).toBe(node);
|
||||||
|
expect(definition.body).toBeNull();
|
||||||
|
expect(definition.helper).toBe(TsHelperFn.Spread);
|
||||||
|
expect(definition.parameters.length).toEqual(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getImportOfIdentifier()', () => {
|
describe('getImportOfIdentifier()', () => {
|
||||||
|
|
Loading…
Reference in New Issue