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.NullKeyword:
|
||||||
case ts.SyntaxKind.TrueKeyword:
|
case ts.SyntaxKind.TrueKeyword:
|
||||||
case ts.SyntaxKind.FalseKeyword:
|
case ts.SyntaxKind.FalseKeyword:
|
||||||
|
case ts.SyntaxKind.TemplateHead:
|
||||||
|
case ts.SyntaxKind.TemplateMiddle:
|
||||||
|
case ts.SyntaxKind.TemplateTail:
|
||||||
return true;
|
return true;
|
||||||
case ts.SyntaxKind.ParenthesizedExpression:
|
case ts.SyntaxKind.ParenthesizedExpression:
|
||||||
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
||||||
|
@ -211,6 +214,10 @@ export class Evaluator {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ts.SyntaxKind.TemplateExpression:
|
||||||
|
const templateExpression = <ts.TemplateExpression>node;
|
||||||
|
return templateExpression.templateSpans.every(
|
||||||
|
span => this.isFoldableWorker(span.expression, folding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -436,9 +443,11 @@ export class Evaluator {
|
||||||
}
|
}
|
||||||
return recordEntry(typeReference, node);
|
return recordEntry(typeReference, node);
|
||||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||||
return (<ts.LiteralExpression>node).text;
|
|
||||||
case ts.SyntaxKind.StringLiteral:
|
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:
|
case ts.SyntaxKind.NumericLiteral:
|
||||||
return parseFloat((<ts.LiteralExpression>node).text);
|
return parseFloat((<ts.LiteralExpression>node).text);
|
||||||
case ts.SyntaxKind.AnyKeyword:
|
case ts.SyntaxKind.AnyKeyword:
|
||||||
|
@ -575,6 +584,36 @@ export class Evaluator {
|
||||||
case ts.SyntaxKind.FunctionExpression:
|
case ts.SyntaxKind.FunctionExpression:
|
||||||
case ts.SyntaxKind.ArrowFunction:
|
case ts.SyntaxKind.ArrowFunction:
|
||||||
return recordEntry(errorSymbol('Function call not supported', node), node);
|
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);
|
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', () => {
|
describe('in strict mode', () => {
|
||||||
it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => {
|
it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => {
|
||||||
const source = program.getSourceFile('/local-symbol-ref.ts');
|
const source = program.getSourceFile('/local-symbol-ref.ts');
|
||||||
|
|
Loading…
Reference in New Issue