-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: String templates #2630
Changes from 10 commits
ca09099
3b9ba72
e13b6f5
ca98178
ab49c95
2eb4eb7
d1c5c04
0300483
a572001
71741b1
e027aa5
dad6fd7
ad23a85
a84c0a6
58f4802
c3b4911
f98ab52
a3f281f
16e7f8b
a4b6d9c
45c8a05
0bbe5aa
07338eb
b0101fe
f66688b
a8ace5c
f103e5a
d023fa8
36248c4
91c4fdc
9867f86
d0ae965
a9f761e
6c18405
08e7b3f
ae003dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,4 @@ model Operation {} | |
model Scalar {} | ||
model Union {} | ||
model UnionVariant {} | ||
model StringTemplate {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,6 +102,14 @@ import { | |
StdTypes, | ||
StringLiteral, | ||
StringLiteralNode, | ||
StringTemplate, | ||
StringTemplateExpressionNode, | ||
StringTemplateHeadNode, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you split this into head/middle/end so that you only capture the part that needs to be computed as the middle? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those are technically slightly different nodes and that's how typescript also structure their template so thought be good to keep inline as they must have had a reason to do that. But right now the data inside is basically the same |
||
StringTemplateMiddleNode, | ||
StringTemplateSpan, | ||
StringTemplateSpanLiteral, | ||
StringTemplateSpanValue, | ||
StringTemplateTailNode, | ||
Sym, | ||
SymbolFlags, | ||
SymbolLinks, | ||
|
@@ -641,6 +649,8 @@ export function createChecker(program: Program): Checker { | |
return checkTupleExpression(node, mapper); | ||
case SyntaxKind.StringLiteral: | ||
return checkStringLiteral(node); | ||
case SyntaxKind.StringTemplateExpression: | ||
return checkStringTemplateExpresion(node, mapper); | ||
case SyntaxKind.ArrayExpression: | ||
return checkArrayExpression(node, mapper); | ||
case SyntaxKind.UnionExpression: | ||
|
@@ -2382,6 +2392,48 @@ export function createChecker(program: Program): Checker { | |
return getMergedSymbol(aliasType.node!.symbol) ?? aliasSymbol; | ||
} | ||
} | ||
|
||
function checkStringTemplateExpresion( | ||
node: StringTemplateExpressionNode, | ||
mapper: TypeMapper | undefined | ||
): StringTemplate { | ||
const spans: StringTemplateSpan[] = [createTemplateSpanLiteral(node.head)]; | ||
for (const span of node.spans) { | ||
spans.push(createTemplateSpanValue(span.expression, mapper)); | ||
spans.push(createTemplateSpanLiteral(span.literal)); | ||
} | ||
const type = createType({ | ||
kind: "StringTemplate", | ||
node, | ||
spans, | ||
}); | ||
|
||
return type; | ||
} | ||
|
||
function createTemplateSpanLiteral( | ||
node: StringTemplateHeadNode | StringTemplateMiddleNode | StringTemplateTailNode | ||
): StringTemplateSpanLiteral { | ||
return createType({ | ||
kind: "StringTemplateSpan", | ||
node: node, | ||
isInterpolated: false, | ||
type: getLiteralType(node), | ||
}); | ||
} | ||
|
||
function createTemplateSpanValue( | ||
node: Expression, | ||
mapper: TypeMapper | undefined | ||
): StringTemplateSpanValue { | ||
return createType({ | ||
kind: "StringTemplateSpan", | ||
node: node, | ||
isInterpolated: true, | ||
type: getTypeForNode(node, mapper), | ||
}); | ||
} | ||
|
||
function checkStringLiteral(str: StringLiteralNode): StringLiteral { | ||
return getLiteralType(str); | ||
} | ||
|
@@ -4058,7 +4110,13 @@ export function createChecker(program: Program): Checker { | |
return finishTypeForProgramAndChecker(program, typePrototype, typeDef); | ||
} | ||
|
||
function getLiteralType(node: StringLiteralNode): StringLiteral; | ||
function getLiteralType( | ||
node: | ||
| StringLiteralNode | ||
| StringTemplateHeadNode | ||
| StringTemplateMiddleNode | ||
| StringTemplateTailNode | ||
): StringLiteral; | ||
function getLiteralType(node: NumericLiteralNode): NumericLiteral; | ||
function getLiteralType(node: BooleanLiteralNode): BooleanLiteral; | ||
function getLiteralType(node: LiteralNode): LiteralType; | ||
|
@@ -4870,16 +4928,23 @@ export function createChecker(program: Program): Checker { | |
} as const); | ||
} | ||
|
||
function createLiteralType(value: string, node?: StringLiteralNode): StringLiteral; | ||
function createLiteralType( | ||
value: string, | ||
node?: | ||
| StringLiteralNode | ||
| StringTemplateHeadNode | ||
| StringTemplateMiddleNode | ||
| StringTemplateTailNode | ||
): StringLiteral; | ||
function createLiteralType(value: number, node?: NumericLiteralNode): NumericLiteral; | ||
function createLiteralType(value: boolean, node?: BooleanLiteralNode): BooleanLiteral; | ||
function createLiteralType( | ||
value: string | number | boolean, | ||
node?: StringLiteralNode | NumericLiteralNode | BooleanLiteralNode | ||
node?: LiteralNode | ||
): StringLiteral | NumericLiteral | BooleanLiteral; | ||
function createLiteralType( | ||
value: string | number | boolean, | ||
node?: StringLiteralNode | NumericLiteralNode | BooleanLiteralNode | ||
node?: LiteralNode | ||
): StringLiteral | NumericLiteral | BooleanLiteral { | ||
if (program.literalTypes.has(value)) { | ||
return program.literalTypes.get(value)!; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { createDiagnostic } from "../messages.js"; | ||
import { Diagnostic, StringTemplate } from "../types.js"; | ||
import { getTypeName } from "./type-name-utils.js"; | ||
|
||
/** | ||
* Convert a string template to a string value. | ||
* Only literal interpolated can be converted to string. | ||
* Otherwise diagnostics will be reported. | ||
* | ||
* @param stringTemplate String template to convert. | ||
*/ | ||
export function stringTemplateToString( | ||
stringTemplate: StringTemplate | ||
): [string, readonly Diagnostic[]] { | ||
const diagnostics: Diagnostic[] = []; | ||
const result = stringTemplate.spans | ||
.map((x) => { | ||
if (x.isInterpolated) { | ||
switch (x.type.kind) { | ||
case "String": | ||
case "Number": | ||
case "Boolean": | ||
return String(x.type.value); | ||
default: | ||
diagnostics.push( | ||
createDiagnostic({ | ||
code: "non-literal-string-template", | ||
target: x.node, | ||
}) | ||
); | ||
return getTypeName(x.type); | ||
} | ||
} else { | ||
return x.type.value; | ||
} | ||
}) | ||
.join(""); | ||
return [result, diagnostics]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the names look good? Alternatives could have been
TemplateString
TemplateStringLiteral
TemplateLiteral
StringTemplateLiteral
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StringTemplateLiteral
is probably more accurateEDIT: Ahh, I didn't see the rest of the names. Probably don't want all of them to be
StringTemplateLiteralX
soStringTemplate
is fine IMO