Skip to content

Commit 42241ec

Browse files
authored
Plumb through TokenFlagsSingleQuote; use for auto import quote detection (#1937)
1 parent 5cb55d4 commit 42241ec

File tree

48 files changed

+280
-1004
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+280
-1004
lines changed

internal/checker/nodebuilderimpl.go

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ func (b *nodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl
532532
specifier = b.getSpecifierForModuleSymbol(chain[0], core.ModuleKindESNext)
533533
attributes = b.f.NewImportAttributes(
534534
ast.KindWithKeyword,
535-
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.f.NewStringLiteral("resolution-mode"), b.f.NewStringLiteral("import"))}),
535+
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.newStringLiteral("resolution-mode"), b.newStringLiteral("import"))}),
536536
false,
537537
)
538538
}
@@ -561,7 +561,7 @@ func (b *nodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl
561561
}
562562
attributes = b.f.NewImportAttributes(
563563
ast.KindWithKeyword,
564-
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.f.NewStringLiteral("resolution-mode"), b.f.NewStringLiteral(modeStr))}),
564+
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.newStringLiteral("resolution-mode"), b.newStringLiteral(modeStr))}),
565565
false,
566566
)
567567
}
@@ -575,7 +575,7 @@ func (b *nodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl
575575
}
576576
}
577577

578-
lit := b.f.NewLiteralTypeNode(b.f.NewStringLiteral(specifier))
578+
lit := b.f.NewLiteralTypeNode(b.newStringLiteral(specifier))
579579
b.ctx.approximateLength += len(specifier) + 10 // specifier + import("")
580580
if nonRootParts == nil || ast.IsEntityName(nonRootParts) {
581581
if nonRootParts != nil {
@@ -692,12 +692,12 @@ func (b *nodeBuilderImpl) createAccessFromSymbolChain(chain []*ast.Symbol, index
692692
if ast.IsIndexedAccessTypeNode(lhs) {
693693
return b.f.NewIndexedAccessTypeNode(
694694
lhs,
695-
b.f.NewLiteralTypeNode(b.f.NewStringLiteral(symbolName)),
695+
b.f.NewLiteralTypeNode(b.newStringLiteral(symbolName)),
696696
)
697697
}
698698
return b.f.NewIndexedAccessTypeNode(
699699
b.f.NewTypeReferenceNode(lhs, typeParameterNodes),
700-
b.f.NewLiteralTypeNode(b.f.NewStringLiteral(symbolName)),
700+
b.f.NewLiteralTypeNode(b.newStringLiteral(symbolName)),
701701
)
702702
}
703703

@@ -736,7 +736,7 @@ func (b *nodeBuilderImpl) createExpressionFromSymbolChain(chain []*ast.Symbol, i
736736
}
737737

738738
if startsWithSingleOrDoubleQuote(symbolName) && core.Some(symbol.Declarations, hasNonGlobalAugmentationExternalModuleSymbol) {
739-
return b.f.NewStringLiteral(b.getSpecifierForModuleSymbol(symbol, core.ResolutionModeNone))
739+
return b.newStringLiteral(b.getSpecifierForModuleSymbol(symbol, core.ResolutionModeNone))
740740
}
741741

742742
if index == 0 || canUsePropertyAccess(symbolName) {
@@ -757,7 +757,7 @@ func (b *nodeBuilderImpl) createExpressionFromSymbolChain(chain []*ast.Symbol, i
757757

758758
var expression *ast.Expression
759759
if startsWithSingleOrDoubleQuote(symbolName) && symbol.Flags&ast.SymbolFlagsEnumMember == 0 {
760-
expression = b.f.NewStringLiteral(stringutil.UnquoteString(symbolName))
760+
expression = b.newStringLiteral(stringutil.UnquoteString(symbolName))
761761
} else if jsnum.FromString(symbolName).String() == symbolName {
762762
// TODO: the follwing in strada would assert if the number is negative, but no such assertion exists here
763763
// Moreover, what's even guaranteeing the name *isn't* -1 here anyway? Needs double-checking.
@@ -2089,7 +2089,7 @@ func (b *nodeBuilderImpl) trackComputedName(accessExpression *ast.Node, enclosin
20892089
}
20902090
}
20912091

2092-
func (b *nodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name string, _singleQuote bool, stringNamed bool, isMethod bool) *ast.Node {
2092+
func (b *nodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name string, singleQuote bool, stringNamed bool, isMethod bool) *ast.Node {
20932093
isMethodNamedNew := isMethod && name == "new"
20942094
if !isMethodNamedNew && scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
20952095
return b.f.NewIdentifier(name)
@@ -2098,7 +2098,9 @@ func (b *nodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name stri
20982098
return b.f.NewNumericLiteral(name)
20992099
}
21002100
result := b.f.NewStringLiteral(name)
2101-
// !!! TODO: set singleQuote
2101+
if singleQuote {
2102+
result.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
2103+
}
21022104
return result
21032105
}
21042106

@@ -2119,10 +2121,8 @@ func (b *nodeBuilderImpl) isStringNamed(d *ast.Declaration) bool {
21192121
}
21202122

21212123
func (b *nodeBuilderImpl) isSingleQuotedStringNamed(d *ast.Declaration) bool {
2122-
return false // !!!
2123-
// TODO: actually support single-quote-style-maintenance
2124-
// name := ast.GetNameOfDeclaration(d)
2125-
// return name != nil && ast.IsStringLiteral(name) && (name.AsStringLiteral().SingleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, false /*includeTrivia*/), "'"))
2124+
name := ast.GetNameOfDeclaration(d)
2125+
return name != nil && ast.IsStringLiteral(name) && name.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0
21262126
}
21272127

21282128
func (b *nodeBuilderImpl) getPropertyNameNodeForSymbol(symbol *ast.Symbol) *ast.Node {
@@ -2164,8 +2164,11 @@ func (b *nodeBuilderImpl) getPropertyNameNodeForSymbolFromNameType(symbol *ast.S
21642164
name = nameType.AsLiteralType().value.(string)
21652165
}
21662166
if !scanner.IsIdentifierText(name, core.LanguageVariantStandard) && (stringNamed || !isNumericLiteralName(name)) {
2167-
// !!! TODO: set singleQuote
2168-
return b.f.NewStringLiteral(name)
2167+
node := b.f.NewStringLiteral(name)
2168+
if singleQuote {
2169+
node.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
2170+
}
2171+
return node
21692172
}
21702173
if isNumericLiteralName(name) && name[0] == '-' {
21712174
return b.f.NewComputedPropertyName(b.f.NewPrefixUnaryExpression(ast.KindMinusToken, b.f.NewNumericLiteral(name[1:])))
@@ -2881,9 +2884,9 @@ func (b *nodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
28812884
if ast.IsImportTypeNode(parentName) {
28822885
parentName.AsImportTypeNode().IsTypeOf = true
28832886
// mutably update, node is freshly manufactured anyhow
2884-
return b.f.NewIndexedAccessTypeNode(parentName, b.f.NewLiteralTypeNode(b.f.NewStringLiteral(memberName)))
2887+
return b.f.NewIndexedAccessTypeNode(parentName, b.f.NewLiteralTypeNode(b.newStringLiteral(memberName)))
28852888
} else if ast.IsTypeReferenceNode(parentName) {
2886-
return b.f.NewIndexedAccessTypeNode(b.f.NewTypeQueryNode(parentName.AsTypeReferenceNode().TypeName, nil), b.f.NewLiteralTypeNode(b.f.NewStringLiteral(memberName)))
2889+
return b.f.NewIndexedAccessTypeNode(b.f.NewTypeQueryNode(parentName.AsTypeReferenceNode().TypeName, nil), b.f.NewLiteralTypeNode(b.newStringLiteral(memberName)))
28872890
} else {
28882891
panic("Unhandled type node kind returned from `symbolToTypeNode`.")
28892892
}
@@ -2892,7 +2895,7 @@ func (b *nodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
28922895
}
28932896
if t.flags&TypeFlagsStringLiteral != 0 {
28942897
b.ctx.approximateLength += len(t.AsLiteralType().value.(string)) + 2
2895-
lit := b.f.NewStringLiteral(t.AsLiteralType().value.(string) /*, b.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0*/)
2898+
lit := b.newStringLiteral(t.AsLiteralType().value.(string))
28962899
b.e.AddEmitFlags(lit, printer.EFNoAsciiEscaping)
28972900
return b.f.NewLiteralTypeNode(lit)
28982901
}
@@ -3105,6 +3108,14 @@ func (b *nodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
31053108
panic("Should be unreachable.")
31063109
}
31073110

3111+
func (b *nodeBuilderImpl) newStringLiteral(text string) *ast.Node {
3112+
node := b.f.NewStringLiteral(text)
3113+
if b.ctx.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0 {
3114+
node.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
3115+
}
3116+
return node
3117+
}
3118+
31083119
// Direct serialization core functions for types, type aliases, and symbols
31093120

31103121
func (t *TypeAlias) ToTypeReferenceNode(b *nodeBuilderImpl) *ast.Node {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
8+
"github.com/microsoft/typescript-go/internal/testutil"
9+
)
10+
11+
func TestAutoImportQuoteDetection(t *testing.T) {
12+
t.Parallel()
13+
14+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
15+
const content = `// @module: esnext
16+
// @Filename: /a.ts
17+
export const foo = 0;
18+
// @Filename: /b.ts
19+
import {} from 'node:path';
20+
21+
fo/**/`
22+
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
23+
f.GoToMarker(t, "")
24+
f.VerifyApplyCodeActionFromCompletion(t, PtrTo(""), &fourslash.ApplyCodeActionFromCompletionOptions{
25+
Name: "foo",
26+
Source: "./a",
27+
Description: "Add import from \"./a\"",
28+
NewFileContent: PtrTo(`import {} from 'node:path';
29+
import { foo } from './a';
30+
31+
fo`),
32+
})
33+
}

internal/ls/autoimportfixes.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,16 @@ func (ct *changeTracker) makeImport(defaultImport *ast.IdentifierNode, namedImpo
265265

266266
func (ct *changeTracker) getNewImports(
267267
moduleSpecifier string,
268-
// quotePreference quotePreference, // !!! quotePreference
268+
quotePreference quotePreference,
269269
defaultImport *Import,
270270
namedImports []*Import,
271271
namespaceLikeImport *Import, // { importKind: ImportKind.CommonJS | ImportKind.Namespace; }
272272
compilerOptions *core.CompilerOptions,
273273
) []*ast.Statement {
274274
moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(moduleSpecifier)
275+
if quotePreference == quotePreferenceSingle {
276+
moduleSpecifierStringLiteral.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
277+
}
275278
var statements []*ast.Statement // []AnyImportSyntax
276279
if defaultImport != nil || len(namedImports) > 0 {
277280
// `verbatimModuleSyntax` should prefer top-level `import type` -

internal/ls/autoimports.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,7 @@ func (l *LanguageService) codeActionForFixWorker(
14121412
if fix.useRequire {
14131413
declarations = changeTracker.getNewRequires(fix.moduleSpecifier, defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
14141414
} else {
1415-
declarations = changeTracker.getNewImports(fix.moduleSpecifier, defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
1415+
declarations = changeTracker.getNewImports(fix.moduleSpecifier, getQuotePreference(sourceFile, l.UserPreferences()), defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
14161416
}
14171417

14181418
changeTracker.insertImports(

internal/ls/utilities.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,27 @@ const (
468468
quotePreferenceDouble
469469
)
470470

471-
// !!!
472-
func getQuotePreference(file *ast.SourceFile, preferences *UserPreferences) quotePreference {
471+
func quotePreferenceFromString(str *ast.StringLiteral) quotePreference {
472+
if str.TokenFlags&ast.TokenFlagsSingleQuote != 0 {
473+
return quotePreferenceSingle
474+
}
475+
return quotePreferenceDouble
476+
}
477+
478+
func getQuotePreference(sourceFile *ast.SourceFile, preferences *UserPreferences) quotePreference {
479+
if preferences.QuotePreference != "" && preferences.QuotePreference != "auto" {
480+
if preferences.QuotePreference == "single" {
481+
return quotePreferenceSingle
482+
}
483+
return quotePreferenceDouble
484+
}
485+
// ignore synthetic import added when importHelpers: true
486+
firstModuleSpecifier := core.Find(sourceFile.Imports(), func(n *ast.Node) bool {
487+
return ast.IsStringLiteral(n) && !ast.NodeIsSynthesized(n.Parent)
488+
})
489+
if firstModuleSpecifier != nil {
490+
return quotePreferenceFromString(firstModuleSpecifier.AsStringLiteral())
491+
}
473492
return quotePreferenceDouble
474493
}
475494

internal/scanner/scanner.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,9 @@ func (s *Scanner) scanIdentifierParts() string {
14551455

14561456
func (s *Scanner) scanString(jsxAttributeString bool) string {
14571457
quote := s.char()
1458+
if quote == '\'' {
1459+
s.tokenFlags |= ast.TokenFlagsSingleQuote
1460+
}
14581461
s.pos++
14591462
// Fast path for simple strings without escape sequences.
14601463
strLen := strings.IndexRune(s.text[s.pos:], quote)

testdata/baselines/reference/submodule/compiler/declarationEmitMappedTypePropertyFromNumericStringKey.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ exports.f = ((arg) => arg)({ '0': 0 }); // Original prop uses string syntax
1212

1313
//// [declarationEmitMappedTypePropertyFromNumericStringKey.d.ts]
1414
export declare const f: {
15-
"0": string | number;
15+
'0': string | number;
1616
};

testdata/baselines/reference/submodule/compiler/declarationEmitMappedTypePropertyFromNumericStringKey.js.diff

Lines changed: 0 additions & 9 deletions
This file was deleted.

testdata/baselines/reference/submodule/compiler/declarationEmitMappedTypePropertyFromNumericStringKey.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
=== declarationEmitMappedTypePropertyFromNumericStringKey.ts ===
44
export const f = (<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}); // Original prop uses string syntax
5-
>f : { "0": string | number; }
6-
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { "0": string | number; }
5+
>f : { '0': string | number; }
6+
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { '0': string | number; }
77
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg) : <T>(arg: { [K in keyof T]: string | T[K]; }) => { [K in keyof T]: string | T[K]; }
88
><T>(arg: {[K in keyof T]: T[K] | string}) => arg : <T>(arg: { [K in keyof T]: string | T[K]; }) => { [K in keyof T]: string | T[K]; }
99
>arg : { [K in keyof T]: string | T[K]; }
1010
>arg : { [K in keyof T]: string | T[K]; }
11-
>{'0': 0} : { "0": number; }
11+
>{'0': 0} : { '0': number; }
1212
>'0' : number
1313
>0 : 0
1414

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
--- old.declarationEmitMappedTypePropertyFromNumericStringKey.types
22
+++ new.declarationEmitMappedTypePropertyFromNumericStringKey.types
3-
@@= skipped -1, +1 lines =@@
4-
5-
=== declarationEmitMappedTypePropertyFromNumericStringKey.ts ===
3+
@@= skipped -3, +3 lines =@@
64
export const f = (<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}); // Original prop uses string syntax
7-
->f : { '0': string | number; }
8-
->(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { '0': string | number; }
5+
>f : { '0': string | number; }
6+
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { '0': string | number; }
97
->(<T>(arg: {[K in keyof T]: T[K] | string}) => arg) : <T>(arg: { [K in keyof T]: T[K] | string; }) => { [K in keyof T]: string | T[K]; }
108
-><T>(arg: {[K in keyof T]: T[K] | string}) => arg : <T>(arg: { [K in keyof T]: T[K] | string; }) => { [K in keyof T]: string | T[K]; }
11-
->arg : { [K in keyof T]: string | T[K]; }
12-
->arg : { [K in keyof T]: string | T[K]; }
13-
->{'0': 0} : { '0': number; }
14-
+>f : { "0": string | number; }
15-
+>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { "0": string | number; }
169
+>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg) : <T>(arg: { [K in keyof T]: string | T[K]; }) => { [K in keyof T]: string | T[K]; }
1710
+><T>(arg: {[K in keyof T]: T[K] | string}) => arg : <T>(arg: { [K in keyof T]: string | T[K]; }) => { [K in keyof T]: string | T[K]; }
18-
+>arg : { [K in keyof T]: string | T[K]; }
19-
+>arg : { [K in keyof T]: string | T[K]; }
20-
+>{'0': 0} : { "0": number; }
21-
>'0' : number
22-
>0 : 0
11+
>arg : { [K in keyof T]: string | T[K]; }
12+
>arg : { [K in keyof T]: string | T[K]; }
13+
>{'0': 0} : { '0': number; }

0 commit comments

Comments
 (0)