Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion packages/compiler-cli/src/ngtsc/partial_evaluator/src/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import ts from 'typescript';

import {DynamicValue} from './dynamic';
import {KnownFn, ResolvedValue, ResolvedValueArray} from './result';
import {EnumValue, KnownFn, ResolvedValue, ResolvedValueArray} from './result';

export class ArraySliceBuiltinFn extends KnownFn {
constructor(private lhs: ResolvedValueArray) {
Expand Down Expand Up @@ -45,6 +45,29 @@ export class ArrayConcatBuiltinFn extends KnownFn {
}
}

export class StringConcatBuiltinFn extends KnownFn {
constructor(private lhs: string) {
super();
}

override evaluate(node: ts.CallExpression, args: ResolvedValueArray): ResolvedValue {
let result = this.lhs;
for (const arg of args) {
const resolved = arg instanceof EnumValue ? arg.resolved : arg;

if (typeof resolved === 'string' || typeof resolved === 'number' ||
typeof resolved === 'boolean' || resolved == null) {
// Cast to `any`, because `concat` will convert
// anything to a string, but TS only allows strings.
result = result.concat(resolved as any);
} else {
return DynamicValue.fromUnknown(node);
}
}
return result;
}
}

export class ObjectAssignBuiltinFn extends KnownFn {
override evaluate(node: ts.CallExpression, args: ResolvedValueArray): ResolvedValue {
if (args.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {DependencyTracker} from '../../incremental/api';
import {Declaration, DeclarationKind, DeclarationNode, EnumMember, FunctionDefinition, isConcreteDeclaration, ReflectionHost, SpecialDeclarationKind} from '../../reflection';
import {isDeclaration} from '../../util/src/typescript';

import {ArrayConcatBuiltinFn, ArraySliceBuiltinFn} from './builtin';
import {ArrayConcatBuiltinFn, ArraySliceBuiltinFn, StringConcatBuiltinFn} from './builtin';
import {DynamicValue} from './dynamic';
import {ForeignFunctionResolver} from './interface';
import {resolveKnownDeclaration} from './known_declaration';
Expand Down Expand Up @@ -399,6 +399,8 @@ export class StaticInterpreter {
return DynamicValue.fromInvalidExpressionType(node, rhs);
}
return lhs[rhs];
} else if (typeof lhs === 'string' && rhs === 'concat') {
return new StringConcatBuiltinFn(lhs);
} else if (lhs instanceof Reference) {
const ref = lhs.node;
if (this.host.isClass(ref)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,17 @@ runInEachFileSystem(() => {
.toBe('a.test.b');
});

it('string `concat` function works', () => {
expect(evaluate(`const a = '12', b = '34';`, 'a[\'concat\'](b)')).toBe('1234');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the tests here appear to test the element access form: a['concat'].
I assume that the changes also support property access form a.concat too? If so, can we add tests to prove it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that these tests don't have any typings so using the .concat access throws a type error. All the other tests use the same trick.

expect(evaluate(`const a = '12', b = '3';`, 'a[\'concat\'](b)')).toBe('123');
expect(evaluate(`const a = '12', b = '3', c = '45';`, 'a[\'concat\'](b,c)')).toBe('12345');
expect(
evaluate(`const a = '1', b = 2, c = '3', d = true, e = null;`, 'a[\'concat\'](b,c,d,e)'))
.toBe('123truenull');
expect(evaluate('enum Test { VALUE = "test" };', '"a."[\'concat\'](Test.VALUE, ".b")'))
.toBe('a.test.b');
});

it('should resolve non-literals as dynamic string', () => {
const value = evaluate(`const a: any = [];`, '`a.${a}.b`');

Expand Down