feat(compiler): do not evaluate metadata expressions that can use references (#18001)
This commit is contained in:

committed by
Igor Minar

parent
72143e80da
commit
ddb766e456
@ -451,6 +451,10 @@ export class MetadataCollector {
|
||||
if (typeof varValue == 'string' || typeof varValue == 'number' ||
|
||||
typeof varValue == 'boolean') {
|
||||
locals.define(nameNode.text, varValue);
|
||||
if (exported) {
|
||||
locals.defineReference(
|
||||
nameNode.text, {__symbolic: 'reference', name: nameNode.text});
|
||||
}
|
||||
} else if (!exported) {
|
||||
if (varValue && !isMetadataError(varValue)) {
|
||||
locals.define(nameNode.text, recordEntry(varValue, node));
|
||||
|
@ -227,7 +227,7 @@ export class Evaluator {
|
||||
* Produce a JSON serialiable object representing `node`. The foldable values in the expression
|
||||
* tree are folded. For example, a node representing `1 + 2` is folded into `3`.
|
||||
*/
|
||||
public evaluateNode(node: ts.Node): MetadataValue {
|
||||
public evaluateNode(node: ts.Node, preferReference?: boolean): MetadataValue {
|
||||
const t = this;
|
||||
let error: MetadataError|undefined;
|
||||
|
||||
@ -240,8 +240,8 @@ export class Evaluator {
|
||||
return !t.options.verboseInvalidExpression && isMetadataError(value);
|
||||
}
|
||||
|
||||
const resolveName = (name: string): MetadataValue => {
|
||||
const reference = this.symbols.resolve(name);
|
||||
const resolveName = (name: string, preferReference?: boolean): MetadataValue => {
|
||||
const reference = this.symbols.resolve(name, preferReference);
|
||||
if (reference === undefined) {
|
||||
// Encode as a global reference. StaticReflector will check the reference.
|
||||
return recordEntry({__symbolic: 'reference', name}, node);
|
||||
@ -268,8 +268,8 @@ export class Evaluator {
|
||||
return true;
|
||||
}
|
||||
const propertyValue = isPropertyAssignment(assignment) ?
|
||||
this.evaluateNode(assignment.initializer) :
|
||||
resolveName(propertyName);
|
||||
this.evaluateNode(assignment.initializer, /* preferReference */ true) :
|
||||
resolveName(propertyName, /* preferReference */ true);
|
||||
if (isFoldableError(propertyValue)) {
|
||||
error = propertyValue;
|
||||
return true; // Stop the forEachChild.
|
||||
@ -286,7 +286,7 @@ export class Evaluator {
|
||||
case ts.SyntaxKind.ArrayLiteralExpression:
|
||||
let arr: MetadataValue[] = [];
|
||||
ts.forEachChild(node, child => {
|
||||
const value = this.evaluateNode(child);
|
||||
const value = this.evaluateNode(child, /* preferReference */ true);
|
||||
|
||||
// Check for error
|
||||
if (isFoldableError(value)) {
|
||||
@ -403,7 +403,7 @@ export class Evaluator {
|
||||
case ts.SyntaxKind.Identifier:
|
||||
const identifier = <ts.Identifier>node;
|
||||
const name = identifier.text;
|
||||
return resolveName(name);
|
||||
return resolveName(name, preferReference);
|
||||
case ts.SyntaxKind.TypeReference:
|
||||
const typeReferenceNode = <ts.TypeReferenceNode>node;
|
||||
const typeNameNode = typeReferenceNode.typeName;
|
||||
|
@ -8,16 +8,22 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {MetadataValue} from './schema';
|
||||
import {MetadataSymbolicReferenceExpression, MetadataValue} from './schema';
|
||||
|
||||
export class Symbols {
|
||||
private _symbols: Map<string, MetadataValue>;
|
||||
private references = new Map<string, MetadataSymbolicReferenceExpression>();
|
||||
|
||||
constructor(private sourceFile: ts.SourceFile) {}
|
||||
|
||||
resolve(name: string): MetadataValue|undefined { return this.symbols.get(name); }
|
||||
resolve(name: string, preferReference?: boolean): MetadataValue|undefined {
|
||||
return (preferReference && this.references.get(name)) || this.symbols.get(name);
|
||||
}
|
||||
|
||||
define(name: string, value: MetadataValue) { this.symbols.set(name, value); }
|
||||
defineReference(name: string, value: MetadataSymbolicReferenceExpression) {
|
||||
this.references.set(name, value);
|
||||
}
|
||||
|
||||
has(name: string): boolean { return this.symbols.has(name); }
|
||||
|
||||
|
@ -635,8 +635,7 @@ describe('Collector', () => {
|
||||
|
||||
describe('with interpolations', () => {
|
||||
function e(expr: string, prefix?: string) {
|
||||
const source = createSource(`${prefix || ''} export let value = ${expr};`);
|
||||
const metadata = collector.getMetadata(source);
|
||||
const metadata = collectSource(`${prefix || ''} export let value = ${expr};`);
|
||||
return expect(metadata.metadata['value']);
|
||||
}
|
||||
|
||||
@ -704,15 +703,12 @@ describe('Collector', () => {
|
||||
});
|
||||
|
||||
it('should ignore |null or |undefined in type expressions', () => {
|
||||
const source = ts.createSourceFile(
|
||||
'somefile.ts', `
|
||||
const metadata = collectSource(`
|
||||
import {Foo} from './foo';
|
||||
export class SomeClass {
|
||||
constructor (a: Foo, b: Foo | null, c: Foo | undefined, d: Foo | undefined | null, e: Foo | undefined | null | Foo) {}
|
||||
}
|
||||
`,
|
||||
ts.ScriptTarget.Latest, true);
|
||||
const metadata = collector.getMetadata(source);
|
||||
`);
|
||||
expect((metadata.metadata['SomeClass'] as ClassMetadata).members).toEqual({
|
||||
__ctor__: [{
|
||||
__symbolic: 'constructor',
|
||||
@ -832,19 +828,18 @@ describe('Collector', () => {
|
||||
|
||||
describe('regerssion', () => {
|
||||
it('should be able to collect a short-hand property value', () => {
|
||||
const source = createSource(`
|
||||
const metadata = collectSource(`
|
||||
const children = { f1: 1 };
|
||||
export const r = [
|
||||
{path: ':locale', children}
|
||||
];
|
||||
`);
|
||||
const metadata = collector.getMetadata(source);
|
||||
expect(metadata.metadata).toEqual({r: [{path: ':locale', children: {f1: 1}}]});
|
||||
});
|
||||
|
||||
// #17518
|
||||
it('should skip a default function', () => {
|
||||
const source = createSource(`
|
||||
const metadata = collectSource(`
|
||||
export default function () {
|
||||
|
||||
const mainRoutes = [
|
||||
@ -856,12 +851,11 @@ describe('Collector', () => {
|
||||
return mainRoutes;
|
||||
|
||||
}`);
|
||||
const metadata = collector.getMetadata(source);
|
||||
expect(metadata).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should skip a named default export', () => {
|
||||
const source = createSource(`
|
||||
const metadata = collectSource(`
|
||||
function mainRoutes() {
|
||||
|
||||
const mainRoutes = [
|
||||
@ -876,7 +870,6 @@ describe('Collector', () => {
|
||||
|
||||
exports = foo;
|
||||
`);
|
||||
const metadata = collector.getMetadata(source);
|
||||
expect(metadata).toBeUndefined();
|
||||
});
|
||||
|
||||
@ -903,11 +896,59 @@ describe('Collector', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('references', () => {
|
||||
beforeEach(() => { collector = new MetadataCollector({quotedNames: true}); });
|
||||
|
||||
it('should record a reference to an exported field of a useValue', () => {
|
||||
const metadata = collectSource(`
|
||||
export var someValue = 1;
|
||||
export const v = {
|
||||
useValue: someValue
|
||||
};
|
||||
`);
|
||||
expect(metadata.metadata['someValue']).toEqual(1);
|
||||
expect(metadata.metadata['v']).toEqual({
|
||||
useValue: {__symbolic: 'reference', name: 'someValue'}
|
||||
});
|
||||
});
|
||||
|
||||
it('should leave external references in place in an object literal', () => {
|
||||
const metadata = collectSource(`
|
||||
export const myLambda = () => [1, 2, 3];
|
||||
const indirect = [{a: 1, b: 3: c: myLambda}];
|
||||
export const v = {
|
||||
v: {i: indirect}
|
||||
}
|
||||
`);
|
||||
expect(metadata.metadata['v']).toEqual({
|
||||
v: {i: [{a: 1, b: 3, c: {__symbolic: 'reference', name: 'myLambda'}}]}
|
||||
});
|
||||
});
|
||||
|
||||
it('should leave an external reference in place in an array literal', () => {
|
||||
const metadata = collectSource(`
|
||||
export const myLambda = () => [1, 2, 3];
|
||||
const indirect = [1, 3, myLambda}];
|
||||
export const v = {
|
||||
v: {i: indirect}
|
||||
}
|
||||
`);
|
||||
expect(metadata.metadata['v']).toEqual({
|
||||
v: {i: [1, 3, {__symbolic: 'reference', name: 'myLambda'}]}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function override(fileName: string, content: string) {
|
||||
host.overrideFile(fileName, content);
|
||||
host.addFile(fileName);
|
||||
program = service.getProgram();
|
||||
}
|
||||
|
||||
function collectSource(content: string): ModuleMetadata {
|
||||
const sourceFile = createSource(content);
|
||||
return collector.getMetadata(sourceFile);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Do not use \` in a template literal as it confuses clang-format
|
||||
|
Reference in New Issue
Block a user