feat(tsc-wrapped): support template literals in metadata collection (#16880)
Fixes: #16876
This commit is contained in:
parent
11c10b2ab8
commit
6e41add867
|
@ -178,6 +178,9 @@ export class Evaluator {
|
|||
case ts.SyntaxKind.NullKeyword:
|
||||
case ts.SyntaxKind.TrueKeyword:
|
||||
case ts.SyntaxKind.FalseKeyword:
|
||||
case ts.SyntaxKind.TemplateHead:
|
||||
case ts.SyntaxKind.TemplateMiddle:
|
||||
case ts.SyntaxKind.TemplateTail:
|
||||
return true;
|
||||
case ts.SyntaxKind.ParenthesizedExpression:
|
||||
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
||||
|
@ -211,6 +214,10 @@ export class Evaluator {
|
|||
return true;
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.TemplateExpression:
|
||||
const templateExpression = <ts.TemplateExpression>node;
|
||||
return templateExpression.templateSpans.every(
|
||||
span => this.isFoldableWorker(span.expression, folding));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -436,9 +443,11 @@ export class Evaluator {
|
|||
}
|
||||
return recordEntry(typeReference, node);
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
return (<ts.LiteralExpression>node).text;
|
||||
case ts.SyntaxKind.StringLiteral:
|
||||
return (<ts.StringLiteral>node).text;
|
||||
case ts.SyntaxKind.TemplateHead:
|
||||
case ts.SyntaxKind.TemplateTail:
|
||||
case ts.SyntaxKind.TemplateMiddle:
|
||||
return (<ts.LiteralLikeNode>node).text;
|
||||
case ts.SyntaxKind.NumericLiteral:
|
||||
return parseFloat((<ts.LiteralExpression>node).text);
|
||||
case ts.SyntaxKind.AnyKeyword:
|
||||
|
@ -575,6 +584,36 @@ export class Evaluator {
|
|||
case ts.SyntaxKind.FunctionExpression:
|
||||
case ts.SyntaxKind.ArrowFunction:
|
||||
return recordEntry(errorSymbol('Function call not supported', node), node);
|
||||
case ts.SyntaxKind.TaggedTemplateExpression:
|
||||
return recordEntry(
|
||||
errorSymbol('Tagged template expressions are not supported in metadata', node), node);
|
||||
case ts.SyntaxKind.TemplateExpression:
|
||||
const templateExpression = <ts.TemplateExpression>node;
|
||||
if (this.isFoldable(node)) {
|
||||
return templateExpression.templateSpans.reduce(
|
||||
(previous, current) => previous + <string>this.evaluateNode(current.expression) +
|
||||
<string>this.evaluateNode(current.literal),
|
||||
this.evaluateNode(templateExpression.head));
|
||||
} else {
|
||||
return templateExpression.templateSpans.reduce((previous, current) => {
|
||||
const expr = this.evaluateNode(current.expression);
|
||||
const literal = this.evaluateNode(current.literal);
|
||||
if (isFoldableError(expr)) return expr;
|
||||
if (isFoldableError(literal)) return literal;
|
||||
if (typeof previous === 'string' && typeof expr === 'string' &&
|
||||
typeof literal === 'string') {
|
||||
return previous + expr + literal;
|
||||
}
|
||||
let result = expr;
|
||||
if (previous !== '') {
|
||||
result = {__symbolic: 'binop', operator: '+', left: previous, right: expr};
|
||||
}
|
||||
if (literal != '') {
|
||||
result = {__symbolic: 'binop', operator: '+', left: result, right: literal};
|
||||
}
|
||||
return result;
|
||||
}, this.evaluateNode(templateExpression.head));
|
||||
}
|
||||
}
|
||||
return recordEntry(errorSymbol('Expression form not supported', node), node);
|
||||
}
|
||||
|
|
|
@ -633,6 +633,80 @@ describe('Collector', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('with interpolations', () => {
|
||||
function createSource(text: string): ts.SourceFile {
|
||||
return ts.createSourceFile('', text, ts.ScriptTarget.Latest, true);
|
||||
}
|
||||
|
||||
function e(expr: string, prefix?: string) {
|
||||
const source = createSource(`${prefix || ''} export let value = ${expr};`);
|
||||
const metadata = collector.getMetadata(source);
|
||||
return expect(metadata.metadata['value']);
|
||||
}
|
||||
|
||||
it('should be able to collect a raw interpolated string',
|
||||
() => { e('`simple value`').toBe('simple value'); });
|
||||
|
||||
it('should be able to interpolate a single value',
|
||||
() => { e('`${foo}`', 'const foo = "foo value"').toBe('foo value'); });
|
||||
|
||||
it('should be able to interpolate multiple values', () => {
|
||||
e('`foo:${foo}, bar:${bar}, end`', 'const foo = "foo"; const bar = "bar";')
|
||||
.toBe('foo:foo, bar:bar, end');
|
||||
});
|
||||
|
||||
it('should be able to interpolate with an imported reference', () => {
|
||||
e('`external:${external}`', 'import {external} from "./external";').toEqual({
|
||||
__symbolic: 'binop',
|
||||
operator: '+',
|
||||
left: 'external:',
|
||||
right: {
|
||||
__symbolic: 'reference',
|
||||
module: './external',
|
||||
name: 'external',
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should simplify a redundant template', () => {
|
||||
e('`${external}`', 'import {external} from "./external";')
|
||||
.toEqual({__symbolic: 'reference', module: './external', name: 'external'});
|
||||
});
|
||||
|
||||
it('should be able to collect complex template with imported references', () => {
|
||||
e('`foo:${foo}, bar:${bar}, end`', 'import {foo, bar} from "./external";').toEqual({
|
||||
__symbolic: 'binop',
|
||||
operator: '+',
|
||||
left: {
|
||||
__symbolic: 'binop',
|
||||
operator: '+',
|
||||
left: {
|
||||
__symbolic: 'binop',
|
||||
operator: '+',
|
||||
left: {
|
||||
__symbolic: 'binop',
|
||||
operator: '+',
|
||||
left: 'foo:',
|
||||
right: {__symbolic: 'reference', module: './external', name: 'foo'}
|
||||
},
|
||||
right: ', bar:'
|
||||
},
|
||||
right: {__symbolic: 'reference', module: './external', name: 'bar'}
|
||||
},
|
||||
right: ', end'
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject a tagged literal', () => {
|
||||
e('tag`some value`').toEqual({
|
||||
__symbolic: 'error',
|
||||
message: 'Tagged template expressions are not supported in metadata',
|
||||
line: 0,
|
||||
character: 20
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in strict mode', () => {
|
||||
it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => {
|
||||
const source = program.getSourceFile('/local-symbol-ref.ts');
|
||||
|
|
Loading…
Reference in New Issue