feat(tsc-wrapped): support template literals in metadata collection (#16880)

Fixes: #16876
This commit is contained in:
Chuck Jazdzewski 2017-05-22 12:19:09 -06:00 committed by GitHub
parent 11c10b2ab8
commit 6e41add867
2 changed files with 115 additions and 2 deletions

View File

@ -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);
}

View File

@ -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');