Skip to content

Commit 975c700

Browse files
committed
Implement quote detection
1 parent 06a7b84 commit 975c700

File tree

6 files changed

+94
-22
lines changed

6 files changed

+94
-22
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
@@ -266,14 +266,17 @@ func (ct *changeTracker) makeImport(defaultImport *ast.IdentifierNode, namedImpo
266266

267267
func (ct *changeTracker) getNewImports(
268268
moduleSpecifier string,
269-
// quotePreference quotePreference, // !!! quotePreference
269+
quotePreference quotePreference,
270270
defaultImport *Import,
271271
namedImports []*Import,
272272
namespaceLikeImport *Import, // { importKind: ImportKind.CommonJS | ImportKind.Namespace; }
273273
compilerOptions *core.CompilerOptions,
274274
preferences *UserPreferences,
275275
) []*ast.Statement {
276276
moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(moduleSpecifier)
277+
if quotePreference == quotePreferenceSingle {
278+
moduleSpecifierStringLiteral.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
279+
}
277280
var statements []*ast.Statement // []AnyImportSyntax
278281
if defaultImport != nil || len(namedImports) > 0 {
279282
// `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
@@ -1436,7 +1436,7 @@ func (l *LanguageService) codeActionForFixWorker(
14361436
if fix.useRequire {
14371437
declarations = changeTracker.getNewRequires(fix.moduleSpecifier, defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options(), preferences)
14381438
} else {
1439-
declarations = changeTracker.getNewImports(fix.moduleSpecifier, defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options(), preferences)
1439+
declarations = changeTracker.getNewImports(fix.moduleSpecifier, getQuotePreference(sourceFile, preferences), defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options(), preferences)
14401440
}
14411441

14421442
changeTracker.insertImports(

internal/ls/utilities.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,30 @@ 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+
var firstModuleSpecifier *ast.Node
487+
if sourceFile.Imports != nil {
488+
firstModuleSpecifier = core.Find(sourceFile.Imports(), func(n *ast.Node) bool {
489+
return ast.IsStringLiteral(n) && !ast.NodeIsSynthesized(n.Parent)
490+
})
491+
}
492+
if firstModuleSpecifier != nil {
493+
return quotePreferenceFromString(firstModuleSpecifier.AsStringLiteral())
494+
}
473495
return quotePreferenceDouble
474496
}
475497

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)

0 commit comments

Comments
 (0)