Skip to content
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

Preserve string delimiter in type printing. #60729

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 59 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1707,10 +1707,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind);
},
typeToString: (type, enclosingDeclaration, flags) => {
return typeToString(type, getParseTreeNode(enclosingDeclaration), flags);
return typeToStringWorker(type, getParseTreeNode(enclosingDeclaration), flags);
},
symbolToString: (symbol, enclosingDeclaration, meaning, flags) => {
return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags);
return symbolToStringWorker(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags);
},
typePredicateToString: (predicate, enclosingDeclaration, flags) => {
return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags);
Expand All @@ -1719,10 +1719,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer);
},
writeType: (type, enclosingDeclaration, flags, writer, verbosityLevel, out) => {
return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer, verbosityLevel, out);
return typeToStringWorker(type, getParseTreeNode(enclosingDeclaration), flags, writer, verbosityLevel, out);
},
writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => {
return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer);
return symbolToStringWorker(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer);
},
writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => {
return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer);
Expand Down Expand Up @@ -5988,8 +5988,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string {
return symbolToStringWorker(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.UseDoubleQuotesForStringLiteralType, writer);
}
function symbolToStringWorker(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string {
let nodeFlags = NodeBuilderFlags.IgnoreErrors;
let internalNodeFlags = InternalNodeBuilderFlags.None;
if (flags & SymbolFormatFlags.UseSingleQuotesForStringLiteralType) {
nodeFlags |= NodeBuilderFlags.UseSingleQuotesForStringLiteralType;
}
if (flags & SymbolFormatFlags.UseDoubleQuotesForStringLiteralType) {
nodeFlags |= NodeBuilderFlags.UseDoubleQuotesForStringLiteralType;
}
if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) {
nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing;
}
Expand Down Expand Up @@ -6039,13 +6048,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

/**
* Prints a type, forcing the conversion of string delimiters to "
* This should usually be used for errors, since in errors printed types are inclosed in '

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This should usually be used for errors, since in errors printed types are inclosed in '
* This should usually be used for errors, since in errors printed types are enclosed in '

*/
function typeToString(
type: Type,
enclosingDeclaration?: Node,
flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
writer: EmitTextWriter = createTextWriter(""),
verbosityLevel?: number,
out?: WriterContextOut,
): string {
return typeToStringWorker(type, enclosingDeclaration, flags | TypeFormatFlags.UseDoubleQuotesForStringLiteralType, writer, verbosityLevel, out);
}

function typeToStringWorker(
type: Type,
enclosingDeclaration?: Node,
flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
writer: EmitTextWriter = createTextWriter(""),
verbosityLevel?: number,
out?: WriterContextOut,
): string {
const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation || verbosityLevel !== undefined;
const typeNode = nodeBuilder.typeToTypeNode(
Expand Down Expand Up @@ -6309,8 +6333,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* It also calls `setOriginalNode` to setup a `.original` pointer, since you basically *always* want these in the node builder.
*/
function setTextRange<T extends Node>(context: NodeBuilderContext, range: T, location: Node | undefined): T {
if (!nodeIsSynthesized(range) || !(range.flags & NodeFlags.Synthesized) || !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(range))) {
range = factory.cloneNode(range); // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions
const nodeSourceFile = getSourceFileOfNode(getOriginalNode(range));
if (!nodeIsSynthesized(range) || !(range.flags & NodeFlags.Synthesized) || !context.enclosingFile || context.enclosingFile !== nodeSourceFile) {
if (range.kind === SyntaxKind.StringLiteral) {
const node = range as Node as StringLiteral;
return setOriginalNode(
context.flags & (NodeBuilderFlags.UseSingleQuotesForStringLiteralType | NodeBuilderFlags.UseDoubleQuotesForStringLiteralType) ?
factory.createStringLiteral(node.text, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)) :
factory.createStringLiteralFromNode(node),
node,
) as Node as T;
}
else {
range = factory.cloneNode(range); // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions
}
}
if (range === location) return range;
if (!location) {
Expand Down Expand Up @@ -8516,10 +8552,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
let firstChar = symbolName.charCodeAt(0);

let useSingleQuote = !(context.flags & NodeBuilderFlags.UseDoubleQuotesForStringLiteralType) &&
(!!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) || firstChar === CharacterCodes.singleQuote);

if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) {
return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context));
return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context), useSingleQuote);
}
if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) {
if (canUsePropertyAccess(symbolName, languageVersion) || (index === 0 && firstChar === CharacterCodes.openBracket)) {
const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping);
if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray<TypeNode | TypeParameterDeclaration>(typeParameterNodes));
identifier.symbol = symbol;
Expand All @@ -8530,21 +8569,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (firstChar === CharacterCodes.openBracket) {
symbolName = symbolName.substring(1, symbolName.length - 1);
firstChar = symbolName.charCodeAt(0);
useSingleQuote = !(context.flags & NodeBuilderFlags.UseDoubleQuotesForStringLiteralType) &&
(!!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) || firstChar === CharacterCodes.singleQuote);
}
let expression: Expression | undefined;
if (isSingleOrDoubleQuote(firstChar) && !(symbol.flags & SymbolFlags.EnumMember)) {
expression = factory.createStringLiteral(stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)), firstChar === CharacterCodes.singleQuote);
const stringLiteralName = factory.createStringLiteral(stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)), useSingleQuote);
stringLiteralName.symbol = symbol;
expression = stringLiteralName;
}
else if (("" + +symbolName) === symbolName) {
expression = factory.createNumericLiteral(+symbolName);
const numberLiteralName = factory.createNumericLiteral(+symbolName);
numberLiteralName.symbol = symbol;
expression = numberLiteralName;
}
if (!expression) {
const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping);
if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray<TypeNode | TypeParameterDeclaration>(typeParameterNodes));
identifier.symbol = symbol;
expression = identifier;
}
return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression);
return index === 0 ? expression : factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression);
}
}
}
Expand Down Expand Up @@ -8572,7 +8617,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) {
const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed);
const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed);
const singleQuote = context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType ? true :
context.flags & NodeBuilderFlags.UseDoubleQuotesForStringLiteralType ? false :
!!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed);
const isMethod = !!(symbol.flags & SymbolFlags.Method);
const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote, stringNamed, isMethod);
if (fromNameType) {
Expand Down
5 changes: 4 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2099,7 +2099,10 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
// SyntaxKind.TemplateTail
function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) {
const text = getLiteralTextOfNode(node, /*sourceFile*/ undefined, printerOptions.neverAsciiEscape, jsxAttributeEscape);
if (
if ((node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) && (node as StringLiteral).symbol) {
writeSymbol(text, (node as StringLiteral).symbol);
}
else if (
(printerOptions.sourceMap || printerOptions.inlineSourceMap)
&& (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))
) {
Expand Down
17 changes: 9 additions & 8 deletions src/compiler/expressionToTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ import {
NodeBuilderFlags,
NodeFlags,
nodeIsMissing,
NoSubstitutionTemplateLiteral,
ObjectLiteralExpression,
ParameterDeclaration,
ParenthesizedExpression,
Expand Down Expand Up @@ -193,7 +192,7 @@ export function createSyntacticTypeNodeBuilder(
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T, range?: Node): T;
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T | undefined, range?: Node): T | undefined;
function reuseNode<T extends Node>(context: SyntacticTypeNodeBuilderContext, node: T | undefined, range: Node | undefined = node) {
return node === undefined ? undefined : resolver.markNodeReuse(context, node.flags & NodeFlags.Synthesized ? node : factory.cloneNode(node), range ?? node);
return node === undefined ? undefined : resolver.markNodeReuse(context, node, range ?? node);
}
function tryReuseExistingTypeNode(context: SyntacticTypeNodeBuilderContext, existing: TypeNode): TypeNode | undefined {
const { finalizeBoundary, startRecoveryScope, hadError, markError } = resolver.createRecoveryBoundary(context);
Expand Down Expand Up @@ -532,10 +531,13 @@ export function createSyntacticTypeNodeBuilder(
setEmitFlags(clone, flags | (context.flags & NodeBuilderFlags.MultilineObjectLiterals && isTypeLiteralNode(node) ? 0 : EmitFlags.SingleLine));
return clone;
}
if (isStringLiteral(node) && !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) && !node.singleQuote) {
const clone = factory.cloneNode(node);
(clone as Mutable<typeof clone>).singleQuote = true;
return clone;
if (isStringLiteral(node)) {
return setOriginalNode(
context.flags & (NodeBuilderFlags.UseSingleQuotesForStringLiteralType | NodeBuilderFlags.UseDoubleQuotesForStringLiteralType) ?
factory.createStringLiteral(node.text, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)) :
factory.createStringLiteralFromNode(node),
node,
);
}
if (isConditionalTypeNode(node)) {
const checkType = visitNode(node.checkType, visitExistingNodeTreeSymbols, isTypeNode)!;
Expand Down Expand Up @@ -942,13 +944,12 @@ export function createSyntacticTypeNodeBuilder(
break;
default:
let typeKind: KeywordTypeSyntaxKind | undefined;
let primitiveNode = node as PrimitiveLiteral;
const primitiveNode = node as PrimitiveLiteral;
switch (node.kind) {
case SyntaxKind.NumericLiteral:
typeKind = SyntaxKind.NumberKeyword;
break;
case SyntaxKind.NoSubstitutionTemplateLiteral:
primitiveNode = factory.createStringLiteral((node as NoSubstitutionTemplateLiteral).text);
typeKind = SyntaxKind.StringKeyword;
break;
case SyntaxKind.StringLiteral:
Expand Down
9 changes: 7 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5489,7 +5489,8 @@ export const enum NodeBuilderFlags {
UseTypeOfFunction = 1 << 12, // Build using typeof instead of function type literal
OmitParameterModifiers = 1 << 13, // Omit modifiers on parameters
UseAliasDefinedOutsideCurrentScope = 1 << 14, // Allow non-visible aliases
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
UseSingleQuotesForStringLiteralType = 1 << 28, // Force single quotes for string literal type or preserve source delimiters
UseDoubleQuotesForStringLiteralType = 1 << 30, // Force double quotes for string literal type or preserve source delimiters
NoTypeReduction = 1 << 29, // Don't call getReducedType
OmitThisParameter = 1 << 25,

Expand Down Expand Up @@ -5544,6 +5545,7 @@ export const enum TypeFormatFlags {

UseAliasDefinedOutsideCurrentScope = 1 << 14, // For a `type T = ... ` defined in a different file, write `T` instead of its value, even though `T` can't be accessed in the current scope.
UseSingleQuotesForStringLiteralType = 1 << 28, // Use single quotes for string literal type
UseDoubleQuotesForStringLiteralType = 1 << 30, // Use double quotes for string literal type
NoTypeReduction = 1 << 29, // Don't call getReducedType
OmitThisParameter = 1 << 25,

Expand All @@ -5563,7 +5565,7 @@ export const enum TypeFormatFlags {
NodeBuilderFlagsMask = NoTruncation | WriteArrayAsGenericType | GenerateNamesForShadowedTypeParams | UseStructuralFallback | WriteTypeArgumentsOfSignature |
UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral |
UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias |
UseSingleQuotesForStringLiteralType | NoTypeReduction | OmitThisParameter,
UseSingleQuotesForStringLiteralType | UseDoubleQuotesForStringLiteralType | NoTypeReduction | OmitThisParameter,
}

// dprint-ignore
Expand Down Expand Up @@ -5592,6 +5594,9 @@ export const enum SymbolFormatFlags {

// Skip building an accessible symbol chain
/** @internal */ DoNotIncludeSymbolChain = 1 << 5,

UseSingleQuotesForStringLiteralType = 1 << 6, // Use single quotes for string literal type
UseDoubleQuotesForStringLiteralType = 1 << 7, // Use double quote for string literal type
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ declare namespace demoNS {
>f : Symbol(f, Decl(demo.d.ts, 0, 26))
}
declare module 'demoModule' {
>'demoModule' : Symbol("demoModule", Decl(demo.d.ts, 2, 1))
>'demoModule' : Symbol('demoModule', Decl(demo.d.ts, 2, 1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised to see symbol baselines change; were we inconsistent with these before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symbol printing is involved in type printing and error printing. I applied the 'preserve source code delimiters in everything except errors' to the symbol printing too. This does mean that the delimiters are now preserved in symbol baselines where they previously were not.

Were they always replaced before? Don't think so. Some error messages used symbol printing for the property names and we can see in error baselines changes that some of those kept the ' delimiter instead of having it replaced with "


import alias = demoNS;
>alias : Symbol(alias, Decl(demo.d.ts, 3, 29))
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ambientDeclarations.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ var q = M1.fn();
// Ambient external module in the global module
// Ambient external module with a string literal name that is a top level external module name
declare module 'external1' {
>'external1' : Symbol("external1", Decl(ambientDeclarations.ts, 67, 16))
>'external1' : Symbol('external1', Decl(ambientDeclarations.ts, 67, 16))

var q;
>q : Symbol(q, Decl(ambientDeclarations.ts, 72, 7))
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/ambientDeclarationsExternal.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var n: number;
=== decls.ts ===
// Ambient external module with export assignment
declare module 'equ' {
>'equ' : Symbol("equ", Decl(decls.ts, 0, 0))
>'equ' : Symbol('equ', Decl(decls.ts, 0, 0))

var x;
>x : Symbol(x, Decl(decls.ts, 2, 7))
Expand All @@ -32,7 +32,7 @@ declare module 'equ' {
}

declare module 'equ2' {
>'equ2' : Symbol("equ2", Decl(decls.ts, 4, 1))
>'equ2' : Symbol('equ2', Decl(decls.ts, 4, 1))

var x: number;
>x : Symbol(x, Decl(decls.ts, 7, 7))
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/ambientErrors.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,16 @@ module M2 {
>M2 : Symbol(M2, Decl(ambientErrors.ts, 42, 1))

declare module 'nope' { }
>'nope' : Symbol("nope", Decl(ambientErrors.ts, 45, 11))
>'nope' : Symbol('nope', Decl(ambientErrors.ts, 45, 11))
}

// Ambient external module with a string literal name that isn't a top level external module name
declare module '../foo' { }
>'../foo' : Symbol("../foo", Decl(ambientErrors.ts, 47, 1))
>'../foo' : Symbol('../foo', Decl(ambientErrors.ts, 47, 1))

// Ambient external module with export assignment and other exported members
declare module 'bar' {
>'bar' : Symbol("bar", Decl(ambientErrors.ts, 50, 27))
>'bar' : Symbol('bar', Decl(ambientErrors.ts, 50, 27))

var n;
>n : Symbol(n, Decl(ambientErrors.ts, 54, 7))
Expand Down
Loading
Loading