Better template literals support in checker (#32064)

* Support template literals in enum declarations

* Support template literals in const enum access

* Support template literals in swith with typeof narrowing

* Support template literals in element access discriminant

* Support template literals in ambient module declaration

* Unify symbols for template literals in computed properties

* Unify expression position checks for template literals

* Support template literals in rename and find all references

* Mark computed properties with template literals as write access

* Inline startsWithQuote
This commit is contained in:
Andrii Dieiev
2019-09-27 12:04:13 -07:00
committed by Nathan Shively-Sanders
parent c0573c59c9
commit a34fdb203e
60 changed files with 2445 additions and 213 deletions
+1 -1
View File
@@ -850,7 +850,7 @@ namespace ts {
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
(isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) ||
isElementAccessExpression(expr) &&
(isStringLiteral(expr.argumentExpression) || isNumericLiteral(expr.argumentExpression)) &&
isStringOrNumericLiteralLike(expr.argumentExpression) &&
isNarrowableReference(expr.expression);
}
+11 -9
View File
@@ -7930,7 +7930,7 @@ namespace ts {
}
function isStringConcatExpression(expr: Node): boolean {
if (expr.kind === SyntaxKind.StringLiteral) {
if (isStringLiteralLike(expr)) {
return true;
}
else if (expr.kind === SyntaxKind.BinaryExpression) {
@@ -7947,6 +7947,7 @@ namespace ts {
switch (expr.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
return true;
case SyntaxKind.PrefixUnaryExpression:
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
@@ -7969,7 +7970,7 @@ namespace ts {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (<EnumDeclaration>declaration).members) {
if (member.initializer && member.initializer.kind === SyntaxKind.StringLiteral) {
if (member.initializer && isStringLiteralLike(member.initializer)) {
return links.enumKind = EnumKind.Literal;
}
if (!isLiteralEnumMember(member)) {
@@ -17921,7 +17922,7 @@ namespace ts {
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
undefined;
}
@@ -18319,8 +18320,8 @@ namespace ts {
const witnesses: (string | undefined)[] = [];
for (const clause of switchStatement.caseBlock.clauses) {
if (clause.kind === SyntaxKind.CaseClause) {
if (clause.expression.kind === SyntaxKind.StringLiteral) {
witnesses.push((clause.expression as StringLiteral).text);
if (isStringLiteralLike(clause.expression)) {
witnesses.push(clause.expression.text);
continue;
}
return emptyArray;
@@ -22887,7 +22888,7 @@ namespace ts {
return objectType;
}
if (isConstEnumObjectType(objectType) && indexExpression.kind !== SyntaxKind.StringLiteral) {
if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) {
error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
return errorType;
}
@@ -31603,7 +31604,8 @@ namespace ts {
}
break;
case SyntaxKind.StringLiteral:
return (<StringLiteral>expr).text;
case SyntaxKind.NoSubstitutionTemplateLiteral:
return (<StringLiteralLike>expr).text;
case SyntaxKind.NumericLiteral:
checkGrammarNumericLiteral(<NumericLiteral>expr);
return +(<NumericLiteral>expr).text;
@@ -31656,7 +31658,7 @@ namespace ts {
return node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((<PropertyAccessExpression>node).expression) ||
node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((<ElementAccessExpression>node).expression) &&
(<ElementAccessExpression>node).argumentExpression.kind === SyntaxKind.StringLiteral;
isStringLiteralLike((<ElementAccessExpression>node).argumentExpression);
}
function checkEnumDeclaration(node: EnumDeclaration) {
@@ -35239,7 +35241,7 @@ namespace ts {
}
function isStringOrNumberLiteralExpression(expr: Expression) {
return expr.kind === SyntaxKind.StringLiteral || expr.kind === SyntaxKind.NumericLiteral ||
return isStringOrNumericLiteralLike(expr) ||
expr.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral;
}
+4 -3
View File
@@ -3249,9 +3249,10 @@ namespace ts {
const substitute = createLiteral(constantValue);
if (!compilerOptions.removeComments) {
const propertyName = isPropertyAccessExpression(node)
? declarationNameToString(node.name)
: getTextOfNode(node.argumentExpression);
const originalNode = getOriginalNode(node, isAccessExpression);
const propertyName = isPropertyAccessExpression(originalNode)
? declarationNameToString(originalNode.name)
: getTextOfNode(originalNode.argumentExpression);
addSyntheticTrailingComment(substitute, SyntaxKind.MultiLineCommentTrivia, ` ${propertyName} `);
}
+9 -6
View File
@@ -1692,7 +1692,6 @@ namespace ts {
case SyntaxKind.ConditionalExpression:
case SyntaxKind.SpreadElement:
case SyntaxKind.TemplateExpression:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.OmittedExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
@@ -1715,6 +1714,7 @@ namespace ts {
case SyntaxKind.NumericLiteral:
case SyntaxKind.BigIntLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.ThisKeyword:
return isInExpressionContext(node);
default:
@@ -2529,6 +2529,7 @@ namespace ts {
const parent = name.parent;
switch (name.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.NumericLiteral:
if (isComputedPropertyName(parent)) return parent.parent;
// falls through
@@ -2556,7 +2557,7 @@ namespace ts {
}
export function isLiteralComputedPropertyDeclarationName(node: Node) {
return (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) &&
return isStringOrNumericLiteralLike(node) &&
node.parent.kind === SyntaxKind.ComputedPropertyName &&
isDeclaration(node.parent.parent);
}
@@ -3243,20 +3244,22 @@ namespace ts {
}
/**
* Strip off existed single quotes or double quotes from a given string
* Strip off existed surrounding single quotes, double quotes, or backticks from a given string
*
* @return non-quoted string
*/
export function stripQuotes(name: string) {
const length = name.length;
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && startsWithQuote(name)) {
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) {
return name.substring(1, length - 1);
}
return name;
}
export function startsWithQuote(name: string): boolean {
return isSingleOrDoubleQuote(name.charCodeAt(0));
function isQuoteOrBacktick(charCode: number) {
return charCode === CharacterCodes.singleQuote ||
charCode === CharacterCodes.doubleQuote ||
charCode === CharacterCodes.backtick;
}
function getReplacement(c: string, offset: number, input: string) {
+1 -1
View File
@@ -2086,7 +2086,7 @@ namespace ts.Completions {
if (name === undefined
// If the symbol is external module, don't show it in the completion list
// (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
|| symbol.flags & SymbolFlags.Module && startsWithQuote(name)
|| symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0))
// If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@"
|| isKnownSymbol(symbol)) {
return undefined;
+3 -2
View File
@@ -448,7 +448,7 @@ namespace ts.FindAllReferences {
function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan {
let start = node.getStart(sourceFile);
let end = (endNode || node).getEnd();
if (node.kind === SyntaxKind.StringLiteral) {
if (isStringLiteralLike(node)) {
Debug.assert(endNode === undefined);
start += 1;
end -= 1;
@@ -1234,8 +1234,9 @@ namespace ts.FindAllReferences.Core {
case SyntaxKind.Identifier:
return (node as Identifier).text.length === searchSymbolName.length;
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.StringLiteral: {
const str = node as StringLiteral;
const str = node as StringLiteralLike;
return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) &&
str.text.length === searchSymbolName.length;
}
+2 -1
View File
@@ -81,7 +81,7 @@ namespace ts.Rename {
function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
let start = node.getStart(sourceFile);
let width = node.getWidth(sourceFile);
if (node.kind === SyntaxKind.StringLiteral) {
if (isStringLiteralLike(node)) {
// Exclude the quotes
start += 1;
width -= 2;
@@ -93,6 +93,7 @@ namespace ts.Rename {
switch (node.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.ThisKeyword:
return true;
case SyntaxKind.NumericLiteral:
+1
View File
@@ -2231,6 +2231,7 @@ namespace ts {
function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined {
switch (node.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.NumericLiteral:
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;
+1 -1
View File
@@ -267,7 +267,7 @@ namespace ts {
isFunctionLike(node.parent) && (<FunctionLikeDeclaration>node.parent).name === node;
}
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral): boolean {
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean {
switch (node.parent.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature: