158 lines
4.9 KiB
Go
158 lines
4.9 KiB
Go
package lsutil
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/astnav"
|
|
"github.com/microsoft/typescript-go/internal/compiler"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
"github.com/microsoft/typescript-go/internal/stringutil"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
func ProbablyUsesSemicolons(file *ast.SourceFile) bool {
|
|
withSemicolon := 0
|
|
withoutSemicolon := 0
|
|
nStatementsToObserve := 5
|
|
|
|
var visit func(node *ast.Node) bool
|
|
visit = func(node *ast.Node) bool {
|
|
if node.Flags&ast.NodeFlagsReparsed != 0 {
|
|
return false
|
|
}
|
|
if SyntaxRequiresTrailingSemicolonOrASI(node.Kind) {
|
|
lastToken := GetLastToken(node, file)
|
|
if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
|
|
withSemicolon++
|
|
} else {
|
|
withoutSemicolon++
|
|
}
|
|
} else if SyntaxRequiresTrailingCommaOrSemicolonOrASI(node.Kind) {
|
|
lastToken := GetLastToken(node, file)
|
|
if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
|
|
withSemicolon++
|
|
} else if lastToken != nil && lastToken.Kind != ast.KindCommaToken {
|
|
lastTokenLine := scanner.GetECMALineOfPosition(
|
|
file,
|
|
astnav.GetStartOfNode(lastToken, file, false /*includeJSDoc*/),
|
|
)
|
|
nextTokenLine := scanner.GetECMALineOfPosition(
|
|
file,
|
|
scanner.SkipTrivia(file.Text(), lastToken.End()),
|
|
)
|
|
// Avoid counting missing semicolon in single-line objects:
|
|
// `function f(p: { x: string /*no semicolon here is insignificant*/ }) {`
|
|
if lastTokenLine != nextTokenLine {
|
|
withoutSemicolon++
|
|
}
|
|
}
|
|
}
|
|
|
|
if withSemicolon+withoutSemicolon >= nStatementsToObserve {
|
|
return true
|
|
}
|
|
|
|
return node.ForEachChild(visit)
|
|
}
|
|
|
|
file.ForEachChild(visit)
|
|
|
|
// One statement missing a semicolon isn't sufficient evidence to say the user
|
|
// doesn't want semicolons, because they may not even be done writing that statement.
|
|
if withSemicolon == 0 && withoutSemicolon <= 1 {
|
|
return true
|
|
}
|
|
|
|
// When both kinds of observation exist, treat the file as using semicolons when the
|
|
// ratio withSemicolon/withoutSemicolon exceeds 1/nStatementsToObserve (real arithmetic),
|
|
// implemented as an integer inequality to avoid truncation.
|
|
if withoutSemicolon == 0 {
|
|
return true
|
|
}
|
|
return withSemicolon*nStatementsToObserve > withoutSemicolon
|
|
}
|
|
|
|
func ShouldUseUriStyleNodeCoreModules(file *ast.SourceFile, program *compiler.Program) core.Tristate {
|
|
for _, node := range file.Imports() {
|
|
if core.NodeCoreModules()[node.Text()] && !core.ExclusivelyPrefixedNodeCoreModules[node.Text()] {
|
|
if strings.HasPrefix(node.Text(), "node:") {
|
|
return core.TSTrue
|
|
} else {
|
|
return core.TSFalse
|
|
}
|
|
}
|
|
}
|
|
|
|
return program.UsesUriStyleNodeCoreModules()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func ModuleSymbolToValidIdentifier(moduleSymbol *ast.Symbol, forceCapitalize bool) string {
|
|
return ModuleSpecifierToValidIdentifier(stringutil.StripQuotes(moduleSymbol.Name), forceCapitalize)
|
|
}
|
|
|
|
func ModuleSpecifierToValidIdentifier(moduleSpecifier string, forceCapitalize bool) string {
|
|
baseName := tspath.GetBaseFileName(strings.TrimSuffix(tspath.RemoveFileExtension(moduleSpecifier), "/index"))
|
|
res := []rune{}
|
|
lastCharWasValid := true
|
|
baseNameRunes := []rune(baseName)
|
|
if len(baseNameRunes) > 0 && scanner.IsIdentifierStart(baseNameRunes[0]) {
|
|
if forceCapitalize {
|
|
res = append(res, unicode.ToUpper(baseNameRunes[0]))
|
|
} else {
|
|
res = append(res, baseNameRunes[0])
|
|
}
|
|
} else {
|
|
lastCharWasValid = false
|
|
}
|
|
|
|
for i := 1; i < len(baseNameRunes); i++ {
|
|
isValid := scanner.IsIdentifierPart(baseNameRunes[i])
|
|
if isValid {
|
|
if !lastCharWasValid {
|
|
res = append(res, unicode.ToUpper(baseNameRunes[i]))
|
|
} else {
|
|
res = append(res, baseNameRunes[i])
|
|
}
|
|
}
|
|
lastCharWasValid = isValid
|
|
}
|
|
|
|
// Need `"_"` to ensure result isn't empty.
|
|
resString := string(res)
|
|
if resString != "" && !IsNonContextualKeyword(scanner.StringToToken(resString)) {
|
|
return resString
|
|
}
|
|
return "_" + resString
|
|
}
|
|
|
|
func IsNonContextualKeyword(token ast.Kind) bool {
|
|
return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token)
|
|
}
|