Skip to content

Commit 6e41add

Browse files
authored
feat(tsc-wrapped): support template literals in metadata collection (#16880)
Fixes: #16876
1 parent 11c10b2 commit 6e41add

File tree

2 files changed

+115
-2
lines changed

2 files changed

+115
-2
lines changed

tools/@angular/tsc-wrapped/src/evaluator.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ export class Evaluator {
178178
case ts.SyntaxKind.NullKeyword:
179179
case ts.SyntaxKind.TrueKeyword:
180180
case ts.SyntaxKind.FalseKeyword:
181+
case ts.SyntaxKind.TemplateHead:
182+
case ts.SyntaxKind.TemplateMiddle:
183+
case ts.SyntaxKind.TemplateTail:
181184
return true;
182185
case ts.SyntaxKind.ParenthesizedExpression:
183186
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
@@ -211,6 +214,10 @@ export class Evaluator {
211214
return true;
212215
}
213216
break;
217+
case ts.SyntaxKind.TemplateExpression:
218+
const templateExpression = <ts.TemplateExpression>node;
219+
return templateExpression.templateSpans.every(
220+
span => this.isFoldableWorker(span.expression, folding));
214221
}
215222
}
216223
return false;
@@ -436,9 +443,11 @@ export class Evaluator {
436443
}
437444
return recordEntry(typeReference, node);
438445
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
439-
return (<ts.LiteralExpression>node).text;
440446
case ts.SyntaxKind.StringLiteral:
441-
return (<ts.StringLiteral>node).text;
447+
case ts.SyntaxKind.TemplateHead:
448+
case ts.SyntaxKind.TemplateTail:
449+
case ts.SyntaxKind.TemplateMiddle:
450+
return (<ts.LiteralLikeNode>node).text;
442451
case ts.SyntaxKind.NumericLiteral:
443452
return parseFloat((<ts.LiteralExpression>node).text);
444453
case ts.SyntaxKind.AnyKeyword:
@@ -575,6 +584,36 @@ export class Evaluator {
575584
case ts.SyntaxKind.FunctionExpression:
576585
case ts.SyntaxKind.ArrowFunction:
577586
return recordEntry(errorSymbol('Function call not supported', node), node);
587+
case ts.SyntaxKind.TaggedTemplateExpression:
588+
return recordEntry(
589+
errorSymbol('Tagged template expressions are not supported in metadata', node), node);
590+
case ts.SyntaxKind.TemplateExpression:
591+
const templateExpression = <ts.TemplateExpression>node;
592+
if (this.isFoldable(node)) {
593+
return templateExpression.templateSpans.reduce(
594+
(previous, current) => previous + <string>this.evaluateNode(current.expression) +
595+
<string>this.evaluateNode(current.literal),
596+
this.evaluateNode(templateExpression.head));
597+
} else {
598+
return templateExpression.templateSpans.reduce((previous, current) => {
599+
const expr = this.evaluateNode(current.expression);
600+
const literal = this.evaluateNode(current.literal);
601+
if (isFoldableError(expr)) return expr;
602+
if (isFoldableError(literal)) return literal;
603+
if (typeof previous === 'string' && typeof expr === 'string' &&
604+
typeof literal === 'string') {
605+
return previous + expr + literal;
606+
}
607+
let result = expr;
608+
if (previous !== '') {
609+
result = {__symbolic: 'binop', operator: '+', left: previous, right: expr};
610+
}
611+
if (literal != '') {
612+
result = {__symbolic: 'binop', operator: '+', left: result, right: literal};
613+
}
614+
return result;
615+
}, this.evaluateNode(templateExpression.head));
616+
}
578617
}
579618
return recordEntry(errorSymbol('Expression form not supported', node), node);
580619
}

tools/@angular/tsc-wrapped/test/collector.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,80 @@ describe('Collector', () => {
633633
});
634634
});
635635

636+
describe('with interpolations', () => {
637+
function createSource(text: string): ts.SourceFile {
638+
return ts.createSourceFile('', text, ts.ScriptTarget.Latest, true);
639+
}
640+
641+
function e(expr: string, prefix?: string) {
642+
const source = createSource(`${prefix || ''} export let value = ${expr};`);
643+
const metadata = collector.getMetadata(source);
644+
return expect(metadata.metadata['value']);
645+
}
646+
647+
it('should be able to collect a raw interpolated string',
648+
() => { e('`simple value`').toBe('simple value'); });
649+
650+
it('should be able to interpolate a single value',
651+
() => { e('`${foo}`', 'const foo = "foo value"').toBe('foo value'); });
652+
653+
it('should be able to interpolate multiple values', () => {
654+
e('`foo:${foo}, bar:${bar}, end`', 'const foo = "foo"; const bar = "bar";')
655+
.toBe('foo:foo, bar:bar, end');
656+
});
657+
658+
it('should be able to interpolate with an imported reference', () => {
659+
e('`external:${external}`', 'import {external} from "./external";').toEqual({
660+
__symbolic: 'binop',
661+
operator: '+',
662+
left: 'external:',
663+
right: {
664+
__symbolic: 'reference',
665+
module: './external',
666+
name: 'external',
667+
}
668+
});
669+
});
670+
671+
it('should simplify a redundant template', () => {
672+
e('`${external}`', 'import {external} from "./external";')
673+
.toEqual({__symbolic: 'reference', module: './external', name: 'external'});
674+
});
675+
676+
it('should be able to collect complex template with imported references', () => {
677+
e('`foo:${foo}, bar:${bar}, end`', 'import {foo, bar} from "./external";').toEqual({
678+
__symbolic: 'binop',
679+
operator: '+',
680+
left: {
681+
__symbolic: 'binop',
682+
operator: '+',
683+
left: {
684+
__symbolic: 'binop',
685+
operator: '+',
686+
left: {
687+
__symbolic: 'binop',
688+
operator: '+',
689+
left: 'foo:',
690+
right: {__symbolic: 'reference', module: './external', name: 'foo'}
691+
},
692+
right: ', bar:'
693+
},
694+
right: {__symbolic: 'reference', module: './external', name: 'bar'}
695+
},
696+
right: ', end'
697+
});
698+
});
699+
700+
it('should reject a tagged literal', () => {
701+
e('tag`some value`').toEqual({
702+
__symbolic: 'error',
703+
message: 'Tagged template expressions are not supported in metadata',
704+
line: 0,
705+
character: 20
706+
});
707+
});
708+
});
709+
636710
describe('in strict mode', () => {
637711
it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => {
638712
const source = program.getSourceFile('/local-symbol-ref.ts');

0 commit comments

Comments
 (0)