Skip to content
Merged
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
47 changes: 29 additions & 18 deletions internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ func (b *nodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl
specifier = b.getSpecifierForModuleSymbol(chain[0], core.ModuleKindESNext)
attributes = b.f.NewImportAttributes(
ast.KindWithKeyword,
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.f.NewStringLiteral("resolution-mode"), b.f.NewStringLiteral("import"))}),
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.newStringLiteral("resolution-mode"), b.newStringLiteral("import"))}),
false,
)
}
Expand Down Expand Up @@ -561,7 +561,7 @@ func (b *nodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl
}
attributes = b.f.NewImportAttributes(
ast.KindWithKeyword,
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.f.NewStringLiteral("resolution-mode"), b.f.NewStringLiteral(modeStr))}),
b.f.NewNodeList([]*ast.Node{b.f.NewImportAttribute(b.newStringLiteral("resolution-mode"), b.newStringLiteral(modeStr))}),
false,
)
}
Expand All @@ -575,7 +575,7 @@ func (b *nodeBuilderImpl) symbolToTypeNode(symbol *ast.Symbol, mask ast.SymbolFl
}
}

lit := b.f.NewLiteralTypeNode(b.f.NewStringLiteral(specifier))
lit := b.f.NewLiteralTypeNode(b.newStringLiteral(specifier))
b.ctx.approximateLength += len(specifier) + 10 // specifier + import("")
if nonRootParts == nil || ast.IsEntityName(nonRootParts) {
if nonRootParts != nil {
Expand Down Expand Up @@ -692,12 +692,12 @@ func (b *nodeBuilderImpl) createAccessFromSymbolChain(chain []*ast.Symbol, index
if ast.IsIndexedAccessTypeNode(lhs) {
return b.f.NewIndexedAccessTypeNode(
lhs,
b.f.NewLiteralTypeNode(b.f.NewStringLiteral(symbolName)),
b.f.NewLiteralTypeNode(b.newStringLiteral(symbolName)),
)
}
return b.f.NewIndexedAccessTypeNode(
b.f.NewTypeReferenceNode(lhs, typeParameterNodes),
b.f.NewLiteralTypeNode(b.f.NewStringLiteral(symbolName)),
b.f.NewLiteralTypeNode(b.newStringLiteral(symbolName)),
)
}

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

if startsWithSingleOrDoubleQuote(symbolName) && core.Some(symbol.Declarations, hasNonGlobalAugmentationExternalModuleSymbol) {
return b.f.NewStringLiteral(b.getSpecifierForModuleSymbol(symbol, core.ResolutionModeNone))
return b.newStringLiteral(b.getSpecifierForModuleSymbol(symbol, core.ResolutionModeNone))
}

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

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

func (b *nodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name string, _singleQuote bool, stringNamed bool, isMethod bool) *ast.Node {
func (b *nodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name string, singleQuote bool, stringNamed bool, isMethod bool) *ast.Node {
isMethodNamedNew := isMethod && name == "new"
if !isMethodNamedNew && scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
return b.f.NewIdentifier(name)
Expand All @@ -2098,7 +2098,9 @@ func (b *nodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name stri
return b.f.NewNumericLiteral(name)
}
result := b.f.NewStringLiteral(name)
// !!! TODO: set singleQuote
if singleQuote {
result.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
}
return result
}

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

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

func (b *nodeBuilderImpl) getPropertyNameNodeForSymbol(symbol *ast.Symbol) *ast.Node {
Expand Down Expand Up @@ -2164,8 +2164,11 @@ func (b *nodeBuilderImpl) getPropertyNameNodeForSymbolFromNameType(symbol *ast.S
name = nameType.AsLiteralType().value.(string)
}
if !scanner.IsIdentifierText(name, core.LanguageVariantStandard) && (stringNamed || !isNumericLiteralName(name)) {
// !!! TODO: set singleQuote
return b.f.NewStringLiteral(name)
node := b.f.NewStringLiteral(name)
if singleQuote {
node.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
}
return node
}
if isNumericLiteralName(name) && name[0] == '-' {
return b.f.NewComputedPropertyName(b.f.NewPrefixUnaryExpression(ast.KindMinusToken, b.f.NewNumericLiteral(name[1:])))
Expand Down Expand Up @@ -2881,9 +2884,9 @@ func (b *nodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
if ast.IsImportTypeNode(parentName) {
parentName.AsImportTypeNode().IsTypeOf = true
// mutably update, node is freshly manufactured anyhow
return b.f.NewIndexedAccessTypeNode(parentName, b.f.NewLiteralTypeNode(b.f.NewStringLiteral(memberName)))
return b.f.NewIndexedAccessTypeNode(parentName, b.f.NewLiteralTypeNode(b.newStringLiteral(memberName)))
} else if ast.IsTypeReferenceNode(parentName) {
return b.f.NewIndexedAccessTypeNode(b.f.NewTypeQueryNode(parentName.AsTypeReferenceNode().TypeName, nil), b.f.NewLiteralTypeNode(b.f.NewStringLiteral(memberName)))
return b.f.NewIndexedAccessTypeNode(b.f.NewTypeQueryNode(parentName.AsTypeReferenceNode().TypeName, nil), b.f.NewLiteralTypeNode(b.newStringLiteral(memberName)))
} else {
panic("Unhandled type node kind returned from `symbolToTypeNode`.")
}
Expand All @@ -2892,7 +2895,7 @@ func (b *nodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
}
if t.flags&TypeFlagsStringLiteral != 0 {
b.ctx.approximateLength += len(t.AsLiteralType().value.(string)) + 2
lit := b.f.NewStringLiteral(t.AsLiteralType().value.(string) /*, b.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0*/)
lit := b.newStringLiteral(t.AsLiteralType().value.(string))
b.e.AddEmitFlags(lit, printer.EFNoAsciiEscaping)
return b.f.NewLiteralTypeNode(lit)
}
Expand Down Expand Up @@ -3105,6 +3108,14 @@ func (b *nodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
panic("Should be unreachable.")
}

func (b *nodeBuilderImpl) newStringLiteral(text string) *ast.Node {
node := b.f.NewStringLiteral(text)
if b.ctx.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0 {
node.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
}
return node
}

// Direct serialization core functions for types, type aliases, and symbols

func (t *TypeAlias) ToTypeReferenceNode(b *nodeBuilderImpl) *ast.Node {
Expand Down
33 changes: 33 additions & 0 deletions internal/fourslash/tests/autoImportQuoteDetection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestAutoImportQuoteDetection(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @module: esnext
// @Filename: /a.ts
export const foo = 0;
// @Filename: /b.ts
import {} from 'node:path';

fo/**/`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.GoToMarker(t, "")
f.VerifyApplyCodeActionFromCompletion(t, PtrTo(""), &fourslash.ApplyCodeActionFromCompletionOptions{
Name: "foo",
Source: "./a",
Description: "Add import from \"./a\"",
NewFileContent: PtrTo(`import {} from 'node:path';
import { foo } from './a';

fo`),
})
}
5 changes: 4 additions & 1 deletion internal/ls/autoimportfixes.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,16 @@ func (ct *changeTracker) makeImport(defaultImport *ast.IdentifierNode, namedImpo

func (ct *changeTracker) getNewImports(
moduleSpecifier string,
// quotePreference quotePreference, // !!! quotePreference
quotePreference quotePreference,
defaultImport *Import,
namedImports []*Import,
namespaceLikeImport *Import, // { importKind: ImportKind.CommonJS | ImportKind.Namespace; }
compilerOptions *core.CompilerOptions,
) []*ast.Statement {
moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(moduleSpecifier)
if quotePreference == quotePreferenceSingle {
moduleSpecifierStringLiteral.AsStringLiteral().TokenFlags |= ast.TokenFlagsSingleQuote
}
var statements []*ast.Statement // []AnyImportSyntax
if defaultImport != nil || len(namedImports) > 0 {
// `verbatimModuleSyntax` should prefer top-level `import type` -
Expand Down
2 changes: 1 addition & 1 deletion internal/ls/autoimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,7 @@ func (l *LanguageService) codeActionForFixWorker(
if fix.useRequire {
declarations = changeTracker.getNewRequires(fix.moduleSpecifier, defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
} else {
declarations = changeTracker.getNewImports(fix.moduleSpecifier, defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
declarations = changeTracker.getNewImports(fix.moduleSpecifier, getQuotePreference(sourceFile, l.UserPreferences()), defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
}

changeTracker.insertImports(
Expand Down
23 changes: 21 additions & 2 deletions internal/ls/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,27 @@ const (
quotePreferenceDouble
)

// !!!
func getQuotePreference(file *ast.SourceFile, preferences *UserPreferences) quotePreference {
func quotePreferenceFromString(str *ast.StringLiteral) quotePreference {
if str.TokenFlags&ast.TokenFlagsSingleQuote != 0 {
return quotePreferenceSingle
}
return quotePreferenceDouble
}

func getQuotePreference(sourceFile *ast.SourceFile, preferences *UserPreferences) quotePreference {
if preferences.QuotePreference != "" && preferences.QuotePreference != "auto" {
if preferences.QuotePreference == "single" {
return quotePreferenceSingle
}
return quotePreferenceDouble
}
// ignore synthetic import added when importHelpers: true
firstModuleSpecifier := core.Find(sourceFile.Imports(), func(n *ast.Node) bool {
return ast.IsStringLiteral(n) && !ast.NodeIsSynthesized(n.Parent)
})
if firstModuleSpecifier != nil {
return quotePreferenceFromString(firstModuleSpecifier.AsStringLiteral())
}
return quotePreferenceDouble
}

Expand Down
3 changes: 3 additions & 0 deletions internal/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,9 @@ func (s *Scanner) scanIdentifierParts() string {

func (s *Scanner) scanString(jsxAttributeString bool) string {
quote := s.char()
if quote == '\'' {
s.tokenFlags |= ast.TokenFlagsSingleQuote
Copy link
Member

Choose a reason for hiding this comment

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

Oops...

Copy link
Member Author

Choose a reason for hiding this comment

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

This wasn't a porting mistake; in Strada we didn’t have this token flag, and we (for some reason unknown to me) intentionally didn't set the single-quote boolean on StringLiteral from the parser. It only existed for synthesized nodes, which means we had to check the source text on parse nodes and the property on synthesized nodes. The way we're doing it now seems like a better design all around. 🤷

Copy link
Member

Choose a reason for hiding this comment

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

Ah, gotcha.

}
s.pos++
// Fast path for simple strings without escape sequences.
strLen := strings.IndexRune(s.text[s.pos:], quote)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ exports.f = ((arg) => arg)({ '0': 0 }); // Original prop uses string syntax

//// [declarationEmitMappedTypePropertyFromNumericStringKey.d.ts]
export declare const f: {
"0": string | number;
'0': string | number;
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

=== declarationEmitMappedTypePropertyFromNumericStringKey.ts ===
export const f = (<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}); // Original prop uses string syntax
>f : { "0": string | number; }
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { "0": string | number; }
>f : { '0': string | number; }
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { '0': string | number; }
>(<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]; }
><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]; }
>arg : { [K in keyof T]: string | T[K]; }
>arg : { [K in keyof T]: string | T[K]; }
>{'0': 0} : { "0": number; }
>{'0': 0} : { '0': number; }
>'0' : number
>0 : 0

Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
--- old.declarationEmitMappedTypePropertyFromNumericStringKey.types
+++ new.declarationEmitMappedTypePropertyFromNumericStringKey.types
@@= skipped -1, +1 lines =@@

=== declarationEmitMappedTypePropertyFromNumericStringKey.ts ===
@@= skipped -3, +3 lines =@@
export const f = (<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}); // Original prop uses string syntax
->f : { '0': string | number; }
->(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { '0': string | number; }
>f : { '0': string | number; }
>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { '0': string | number; }
->(<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]; }
-><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]; }
->arg : { [K in keyof T]: string | T[K]; }
->arg : { [K in keyof T]: string | T[K]; }
->{'0': 0} : { '0': number; }
+>f : { "0": string | number; }
+>(<T>(arg: {[K in keyof T]: T[K] | string}) => arg)({'0': 0}) : { "0": string | number; }
+>(<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]; }
+><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]; }
+>arg : { [K in keyof T]: string | T[K]; }
+>arg : { [K in keyof T]: string | T[K]; }
+>{'0': 0} : { "0": number; }
>'0' : number
>0 : 0
>arg : { [K in keyof T]: string | T[K]; }
>arg : { [K in keyof T]: string | T[K]; }
>{'0': 0} : { '0': number; }
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export declare enum MouseButton {
NO_BUTTON = 0
}
export declare const DOMMouseButton: {
"-1": MouseButton;
'-1': MouseButton;
"0": MouseButton;
"1": MouseButton;
"2": MouseButton;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export enum MouseButton {
}

export const DOMMouseButton = {
>DOMMouseButton : { "-1": MouseButton; "0": MouseButton; "1": MouseButton; "2": MouseButton; "3": MouseButton; "4": MouseButton; }
>{ '-1': MouseButton.NO_BUTTON, "0": MouseButton.LEFT_BUTTON, "1": MouseButton.MIDDLE_BUTTON, "2": MouseButton.RIGHT_BUTTON, "3": MouseButton.XBUTTON1_BUTTON, "4": MouseButton.XBUTTON2_BUTTON,} : { "-1": MouseButton; "0": MouseButton; "1": MouseButton; "2": MouseButton; "3": MouseButton; "4": MouseButton; }
>DOMMouseButton : { '-1': MouseButton; "0": MouseButton; "1": MouseButton; "2": MouseButton; "3": MouseButton; "4": MouseButton; }
>{ '-1': MouseButton.NO_BUTTON, "0": MouseButton.LEFT_BUTTON, "1": MouseButton.MIDDLE_BUTTON, "2": MouseButton.RIGHT_BUTTON, "3": MouseButton.XBUTTON1_BUTTON, "4": MouseButton.XBUTTON2_BUTTON,} : { '-1': MouseButton; "0": MouseButton; "1": MouseButton; "2": MouseButton; "3": MouseButton; "4": MouseButton; }

'-1': MouseButton.NO_BUTTON,
>'-1' : MouseButton
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/index.ts(7,7): error TS2322: Type '{ default: { configs: { "stage-0": PluginConfig; }; }; configs: { "stage-0": PluginConfig; }; }' is not assignable to type 'Plugin'.
/index.ts(7,7): error TS2322: Type '{ default: { configs: { 'stage-0': PluginConfig; }; }; configs: { 'stage-0': PluginConfig; }; }' is not assignable to type 'Plugin'.
Types of property 'configs' are incompatible.
Type '{ "stage-0": PluginConfig; }' is not assignable to type 'Record<string, { parser: string | null; }>'.
Type '{ 'stage-0': PluginConfig; }' is not assignable to type 'Record<string, { parser: string | null; }>'.
Property ''stage-0'' is incompatible with index signature.
Type 'PluginConfig' is not assignable to type '{ parser: string | null; }'.
Types of property 'parser' are incompatible.
Expand Down Expand Up @@ -43,9 +43,9 @@

const p: Plugin = pluginImportX;
~
!!! error TS2322: Type '{ default: { configs: { "stage-0": PluginConfig; }; }; configs: { "stage-0": PluginConfig; }; }' is not assignable to type 'Plugin'.
!!! error TS2322: Type '{ default: { configs: { 'stage-0': PluginConfig; }; }; configs: { 'stage-0': PluginConfig; }; }' is not assignable to type 'Plugin'.
!!! error TS2322: Types of property 'configs' are incompatible.
!!! error TS2322: Type '{ "stage-0": PluginConfig; }' is not assignable to type 'Record<string, { parser: string | null; }>'.
!!! error TS2322: Type '{ 'stage-0': PluginConfig; }' is not assignable to type 'Record<string, { parser: string | null; }>'.
!!! error TS2322: Property ''stage-0'' is incompatible with index signature.
!!! error TS2322: Type 'PluginConfig' is not assignable to type '{ parser: string | null; }'.
!!! error TS2322: Types of property 'parser' are incompatible.
Expand Down
Loading