1845 lines
63 KiB
Go
1845 lines
63 KiB
Go
package checker
|
|
|
|
import (
|
|
"cmp"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"unicode/utf8"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/binder"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/debug"
|
|
"github.com/microsoft/typescript-go/internal/diagnostics"
|
|
"github.com/microsoft/typescript-go/internal/jsnum"
|
|
"github.com/microsoft/typescript-go/internal/module"
|
|
"github.com/microsoft/typescript-go/internal/printer"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
func NewDiagnosticForNode(node *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
|
|
var file *ast.SourceFile
|
|
var loc core.TextRange
|
|
if node != nil {
|
|
file = ast.GetSourceFileOfNode(node)
|
|
loc = scanner.GetErrorRangeForNode(file, node)
|
|
}
|
|
return ast.NewDiagnostic(file, loc, message, args...)
|
|
}
|
|
|
|
func NewDiagnosticChainForNode(chain *ast.Diagnostic, node *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
|
|
if chain != nil {
|
|
return ast.NewDiagnosticChain(chain, message, args...)
|
|
}
|
|
return NewDiagnosticForNode(node, message, args...)
|
|
}
|
|
|
|
func findInMap[K comparable, V any](m map[K]V, predicate func(V) bool) V {
|
|
for _, value := range m {
|
|
if predicate(value) {
|
|
return value
|
|
}
|
|
}
|
|
return *new(V)
|
|
}
|
|
|
|
func tokenIsIdentifierOrKeyword(token ast.Kind) bool {
|
|
return token >= ast.KindIdentifier
|
|
}
|
|
|
|
func tokenIsIdentifierOrKeywordOrGreaterThan(token ast.Kind) bool {
|
|
return token == ast.KindGreaterThanToken || tokenIsIdentifierOrKeyword(token)
|
|
}
|
|
|
|
func hasOverrideModifier(node *ast.Node) bool {
|
|
return ast.HasSyntacticModifier(node, ast.ModifierFlagsOverride)
|
|
}
|
|
|
|
func hasAsyncModifier(node *ast.Node) bool {
|
|
return ast.HasSyntacticModifier(node, ast.ModifierFlagsAsync)
|
|
}
|
|
|
|
func getSelectedModifierFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags {
|
|
return node.ModifierFlags() & flags
|
|
}
|
|
|
|
func hasReadonlyModifier(node *ast.Node) bool {
|
|
return ast.HasModifier(node, ast.ModifierFlagsReadonly)
|
|
}
|
|
|
|
func isStaticPrivateIdentifierProperty(s *ast.Symbol) bool {
|
|
return s.ValueDeclaration != nil && ast.IsPrivateIdentifierClassElementDeclaration(s.ValueDeclaration) && ast.IsStatic(s.ValueDeclaration)
|
|
}
|
|
|
|
func isEmptyObjectLiteral(expression *ast.Node) bool {
|
|
return ast.IsObjectLiteralExpression(expression) && len(expression.Properties()) == 0
|
|
}
|
|
|
|
type AssignmentKind int32
|
|
|
|
const (
|
|
AssignmentKindNone AssignmentKind = iota
|
|
AssignmentKindDefinite
|
|
AssignmentKindCompound
|
|
)
|
|
|
|
type AssignmentTarget = ast.Node // BinaryExpression | PrefixUnaryExpression | PostfixUnaryExpression | ForInOrOfStatement
|
|
|
|
func getAssignmentTargetKind(node *ast.Node) AssignmentKind {
|
|
target := ast.GetAssignmentTarget(node)
|
|
if target == nil {
|
|
return AssignmentKindNone
|
|
}
|
|
switch target.Kind {
|
|
case ast.KindBinaryExpression:
|
|
binaryOperator := target.AsBinaryExpression().OperatorToken.Kind
|
|
if binaryOperator == ast.KindEqualsToken || ast.IsLogicalOrCoalescingAssignmentOperator(binaryOperator) {
|
|
return AssignmentKindDefinite
|
|
}
|
|
return AssignmentKindCompound
|
|
case ast.KindPrefixUnaryExpression, ast.KindPostfixUnaryExpression:
|
|
return AssignmentKindCompound
|
|
case ast.KindForInStatement, ast.KindForOfStatement:
|
|
return AssignmentKindDefinite
|
|
}
|
|
panic("Unhandled case in getAssignmentTargetKind")
|
|
}
|
|
|
|
func isDeleteTarget(node *ast.Node) bool {
|
|
if !ast.IsAccessExpression(node) {
|
|
return false
|
|
}
|
|
node = ast.WalkUpParenthesizedExpressions(node.Parent)
|
|
return node != nil && node.Kind == ast.KindDeleteExpression
|
|
}
|
|
|
|
func isInCompoundLikeAssignment(node *ast.Node) bool {
|
|
target := ast.GetAssignmentTarget(node)
|
|
return target != nil && ast.IsAssignmentExpression(target /*excludeCompoundAssignment*/, true) && isCompoundLikeAssignment(target)
|
|
}
|
|
|
|
func isCompoundLikeAssignment(assignment *ast.Node) bool {
|
|
right := ast.SkipParentheses(assignment.AsBinaryExpression().Right)
|
|
return right.Kind == ast.KindBinaryExpression && isShiftOperatorOrHigher(right.AsBinaryExpression().OperatorToken.Kind)
|
|
}
|
|
|
|
func isConstTypeReference(node *ast.Node) bool {
|
|
return ast.IsTypeReferenceNode(node) && len(node.TypeArguments()) == 0 && ast.IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.AsTypeReferenceNode().TypeName.Text() == "const"
|
|
}
|
|
|
|
func GetSingleVariableOfVariableStatement(node *ast.Node) *ast.Node {
|
|
if !ast.IsVariableStatement(node) {
|
|
return nil
|
|
}
|
|
return core.FirstOrNil(node.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes)
|
|
}
|
|
|
|
func isTypeReferenceIdentifier(node *ast.Node) bool {
|
|
for node.Parent.Kind == ast.KindQualifiedName {
|
|
node = node.Parent
|
|
}
|
|
return ast.IsTypeReferenceNode(node.Parent)
|
|
}
|
|
|
|
func IsInTypeQuery(node *ast.Node) bool {
|
|
// TypeScript 1.0 spec (April 2014): 3.6.3
|
|
// A type query consists of the keyword typeof followed by an expression.
|
|
// The expression is restricted to a single identifier or a sequence of identifiers separated by periods
|
|
return ast.FindAncestorOrQuit(node, func(n *ast.Node) ast.FindAncestorResult {
|
|
switch n.Kind {
|
|
case ast.KindTypeQuery:
|
|
return ast.FindAncestorTrue
|
|
case ast.KindIdentifier, ast.KindQualifiedName:
|
|
return ast.FindAncestorFalse
|
|
}
|
|
return ast.FindAncestorQuit
|
|
}) != nil
|
|
}
|
|
|
|
func canHaveLocals(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindArrowFunction, ast.KindBlock, ast.KindCallSignature, ast.KindCaseBlock, ast.KindCatchClause,
|
|
ast.KindClassStaticBlockDeclaration, ast.KindConditionalType, ast.KindConstructor, ast.KindConstructorType,
|
|
ast.KindConstructSignature, ast.KindForStatement, ast.KindForInStatement, ast.KindForOfStatement, ast.KindFunctionDeclaration,
|
|
ast.KindFunctionExpression, ast.KindFunctionType, ast.KindGetAccessor, ast.KindIndexSignature,
|
|
ast.KindJSDocSignature, ast.KindMappedType,
|
|
ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindModuleDeclaration, ast.KindSetAccessor, ast.KindSourceFile,
|
|
ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isShorthandAmbientModuleSymbol(moduleSymbol *ast.Symbol) bool {
|
|
return isShorthandAmbientModule(moduleSymbol.ValueDeclaration)
|
|
}
|
|
|
|
func isShorthandAmbientModule(node *ast.Node) bool {
|
|
// The only kind of module that can be missing a body is a shorthand ambient module.
|
|
return node != nil && node.Kind == ast.KindModuleDeclaration && node.Body() == nil
|
|
}
|
|
|
|
func getAliasDeclarationFromName(node *ast.Node) *ast.Node {
|
|
switch node.Parent.Kind {
|
|
case ast.KindImportClause, ast.KindImportSpecifier, ast.KindNamespaceImport, ast.KindExportSpecifier, ast.KindExportAssignment,
|
|
ast.KindImportEqualsDeclaration, ast.KindNamespaceExport:
|
|
return node.Parent
|
|
case ast.KindQualifiedName:
|
|
return getAliasDeclarationFromName(node.Parent)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func entityNameToString(name *ast.Node) string {
|
|
return ast.EntityNameToString(name, scanner.GetTextOfNode)
|
|
}
|
|
|
|
func getContainingQualifiedNameNode(node *ast.Node) *ast.Node {
|
|
for ast.IsQualifiedName(node.Parent) {
|
|
node = node.Parent
|
|
}
|
|
return node
|
|
}
|
|
|
|
func isSideEffectImport(node *ast.Node) bool {
|
|
ancestor := ast.FindAncestor(node, ast.IsImportDeclaration)
|
|
return ancestor != nil && ancestor.ImportClause() == nil
|
|
}
|
|
|
|
func getExternalModuleRequireArgument(node *ast.Node) *ast.Node {
|
|
if ast.IsVariableDeclarationInitializedToRequire(node) {
|
|
return node.Initializer().Arguments()[0]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isRightSideOfAccessExpression(node *ast.Node) bool {
|
|
return node.Parent != nil && (ast.IsPropertyAccessExpression(node.Parent) && node.Parent.Name() == node ||
|
|
ast.IsElementAccessExpression(node.Parent) && node.Parent.AsElementAccessExpression().ArgumentExpression == node)
|
|
}
|
|
|
|
func isTopLevelInExternalModuleAugmentation(node *ast.Node) bool {
|
|
return node != nil && node.Parent != nil && ast.IsModuleBlock(node.Parent) && ast.IsExternalModuleAugmentation(node.Parent.Parent)
|
|
}
|
|
|
|
func isSyntacticDefault(node *ast.Node) bool {
|
|
return (ast.IsExportAssignment(node) && !node.AsExportAssignment().IsExportEquals) ||
|
|
ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) ||
|
|
ast.IsExportSpecifier(node) ||
|
|
ast.IsNamespaceExport(node)
|
|
}
|
|
|
|
func hasExportAssignmentSymbol(moduleSymbol *ast.Symbol) bool {
|
|
return moduleSymbol.Exports[ast.InternalSymbolNameExportEquals] != nil
|
|
}
|
|
|
|
func isTypeAlias(node *ast.Node) bool {
|
|
return ast.IsTypeOrJSTypeAliasDeclaration(node)
|
|
}
|
|
|
|
func hasOnlyExpressionInitializer(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindVariableDeclaration, ast.KindParameter, ast.KindBindingElement, ast.KindPropertyDeclaration, ast.KindPropertyAssignment, ast.KindEnumMember:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasDotDotDotToken(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindParameter:
|
|
return node.AsParameterDeclaration().DotDotDotToken != nil
|
|
case ast.KindBindingElement:
|
|
return node.AsBindingElement().DotDotDotToken != nil
|
|
case ast.KindNamedTupleMember:
|
|
return node.AsNamedTupleMember().DotDotDotToken != nil
|
|
case ast.KindJsxExpression:
|
|
return node.AsJsxExpression().DotDotDotToken != nil
|
|
}
|
|
return false
|
|
}
|
|
|
|
func IsTypeAny(t *Type) bool {
|
|
return t != nil && t.flags&TypeFlagsAny != 0
|
|
}
|
|
|
|
func isJSDocOptionalParameter(node *ast.ParameterDeclaration) bool {
|
|
return false // !!!
|
|
}
|
|
|
|
func isExclamationToken(node *ast.Node) bool {
|
|
return node != nil && node.Kind == ast.KindExclamationToken
|
|
}
|
|
|
|
func isOptionalDeclaration(declaration *ast.Node) bool {
|
|
return ast.HasQuestionToken(declaration)
|
|
}
|
|
|
|
func (c *Checker) isOptionalParameter(node *ast.Node) bool {
|
|
// !!! TODO: JSDoc support
|
|
if ast.IsParameterDeclaration(node) && node.QuestionToken() != nil {
|
|
return true
|
|
}
|
|
if !ast.IsParameterDeclaration(node) {
|
|
return false
|
|
}
|
|
if node.Initializer() != nil {
|
|
signature := c.getSignatureFromDeclaration(node.Parent)
|
|
parameterIndex := core.FindIndex(node.Parent.Parameters(), func(p *ast.ParameterDeclarationNode) bool { return p == node })
|
|
debug.Assert(parameterIndex >= 0)
|
|
// Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used
|
|
// in grammar checks and checking for `void` too early results in parameter types widening too early
|
|
// and causes some noImplicitAny errors to be lost.
|
|
return parameterIndex >= c.getMinArgumentCountEx(signature, MinArgumentCountFlagsStrongArityForUntypedJS|MinArgumentCountFlagsVoidIsNonOptional)
|
|
}
|
|
iife := ast.GetImmediatelyInvokedFunctionExpression(node.Parent)
|
|
if iife != nil {
|
|
parameterIndex := core.FindIndex(node.Parent.Parameters(), func(p *ast.ParameterDeclarationNode) bool { return p == node })
|
|
return node.Type() == nil &&
|
|
node.AsParameterDeclaration().DotDotDotToken == nil &&
|
|
parameterIndex >= len(c.getEffectiveCallArguments(iife))
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isEmptyArrayLiteral(expression *ast.Node) bool {
|
|
return ast.IsArrayLiteralExpression(expression) && len(expression.Elements()) == 0
|
|
}
|
|
|
|
func declarationBelongsToPrivateAmbientMember(declaration *ast.Node) bool {
|
|
root := ast.GetRootDeclaration(declaration)
|
|
memberDeclaration := root
|
|
if root.Kind == ast.KindParameter {
|
|
memberDeclaration = root.Parent
|
|
}
|
|
return isPrivateWithinAmbient(memberDeclaration)
|
|
}
|
|
|
|
func isPrivateWithinAmbient(node *ast.Node) bool {
|
|
return (ast.HasModifier(node, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifierClassElementDeclaration(node)) && node.Flags&ast.NodeFlagsAmbient != 0
|
|
}
|
|
|
|
func isTypeAssertion(node *ast.Node) bool {
|
|
return ast.IsAssertionExpression(ast.SkipParentheses(node))
|
|
}
|
|
|
|
func createSymbolTable(symbols []*ast.Symbol) ast.SymbolTable {
|
|
if len(symbols) == 0 {
|
|
return nil
|
|
}
|
|
result := make(ast.SymbolTable)
|
|
for _, symbol := range symbols {
|
|
result[symbol.Name] = symbol
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (c *Checker) sortSymbols(symbols []*ast.Symbol) {
|
|
slices.SortFunc(symbols, c.compareSymbols)
|
|
}
|
|
|
|
func (c *Checker) compareSymbolsWorker(s1, s2 *ast.Symbol) int {
|
|
if s1 == s2 {
|
|
return 0
|
|
}
|
|
if s1 == nil {
|
|
return 1
|
|
}
|
|
if s2 == nil {
|
|
return -1
|
|
}
|
|
if len(s1.Declarations) != 0 && len(s2.Declarations) != 0 {
|
|
if r := c.compareNodes(s1.Declarations[0], s2.Declarations[0]); r != 0 {
|
|
return r
|
|
}
|
|
} else if len(s1.Declarations) != 0 {
|
|
return -1
|
|
} else if len(s2.Declarations) != 0 {
|
|
return 1
|
|
}
|
|
if r := strings.Compare(s1.Name, s2.Name); r != 0 {
|
|
return r
|
|
}
|
|
// Fall back to symbol IDs. This is a last resort that should happen only when symbols have
|
|
// no declaration and duplicate names.
|
|
return int(ast.GetSymbolId(s1)) - int(ast.GetSymbolId(s2))
|
|
}
|
|
|
|
func (c *Checker) compareNodes(n1, n2 *ast.Node) int {
|
|
if n1 == n2 {
|
|
return 0
|
|
}
|
|
if n1 == nil {
|
|
return 1
|
|
}
|
|
if n2 == nil {
|
|
return -1
|
|
}
|
|
s1 := ast.GetSourceFileOfNode(n1)
|
|
s2 := ast.GetSourceFileOfNode(n2)
|
|
if s1 != s2 {
|
|
f1 := c.fileIndexMap[s1]
|
|
f2 := c.fileIndexMap[s2]
|
|
// Order by index of file in the containing program
|
|
return f1 - f2
|
|
}
|
|
// In the same file, order by source position
|
|
return n1.Pos() - n2.Pos()
|
|
}
|
|
|
|
func CompareTypes(t1, t2 *Type) int {
|
|
if t1 == t2 {
|
|
return 0
|
|
}
|
|
if t1 == nil {
|
|
return -1
|
|
}
|
|
if t2 == nil {
|
|
return 1
|
|
}
|
|
if t1.checker != t2.checker {
|
|
panic("Cannot compare types from different checkers")
|
|
}
|
|
// First sort in order of increasing type flags values.
|
|
if c := getSortOrderFlags(t1) - getSortOrderFlags(t2); c != 0 {
|
|
return c
|
|
}
|
|
// Order named types by name and, in the case of aliased types, by alias type arguments.
|
|
if c := compareTypeNames(t1, t2); c != 0 {
|
|
return c
|
|
}
|
|
// We have unnamed types or types with identical names. Now sort by data specific to the type.
|
|
switch {
|
|
case t1.flags&(TypeFlagsAny|TypeFlagsUnknown|TypeFlagsString|TypeFlagsNumber|TypeFlagsBoolean|TypeFlagsBigInt|TypeFlagsESSymbol|TypeFlagsVoid|TypeFlagsUndefined|TypeFlagsNull|TypeFlagsNever|TypeFlagsNonPrimitive) != 0:
|
|
// Only distinguished by type IDs, handled below.
|
|
case t1.flags&TypeFlagsObject != 0:
|
|
// Order unnamed or identically named object types by symbol.
|
|
if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 {
|
|
return c
|
|
}
|
|
// When object types have the same or no symbol, order by kind. We order type references before other kinds.
|
|
if t1.objectFlags&ObjectFlagsReference != 0 && t2.objectFlags&ObjectFlagsReference != 0 {
|
|
r1 := t1.AsTypeReference()
|
|
r2 := t2.AsTypeReference()
|
|
if r1.target.objectFlags&ObjectFlagsTuple != 0 && r2.target.objectFlags&ObjectFlagsTuple != 0 {
|
|
// Tuple types have no associated symbol, instead we order by tuple element information.
|
|
if c := compareTupleTypes(r1.target.AsTupleType(), r2.target.AsTupleType()); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
// Here we know we have references to instantiations of the same type because we have matching targets.
|
|
if r1.node == nil && r2.node == nil {
|
|
// Non-deferred type references with the same target are sorted by their type argument lists.
|
|
if c := compareTypeLists(t1.AsTypeReference().resolvedTypeArguments, t2.AsTypeReference().resolvedTypeArguments); c != 0 {
|
|
return c
|
|
}
|
|
} else {
|
|
// Deferred type references with the same target are ordered by the source location of the reference.
|
|
if c := t1.checker.compareNodes(r1.node, r2.node); c != 0 {
|
|
return c
|
|
}
|
|
// Instantiations of the same deferred type reference are ordered by their associated type mappers
|
|
// (which reflect the mapping of in-scope type parameters to type arguments).
|
|
if c := compareTypeMappers(t1.AsObjectType().mapper, t2.AsObjectType().mapper); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
} else if t1.objectFlags&ObjectFlagsReference != 0 {
|
|
return -1
|
|
} else if t2.objectFlags&ObjectFlagsReference != 0 {
|
|
return 1
|
|
} else {
|
|
// Order unnamed non-reference object types by kind associated type mappers. Reverse mapped types have
|
|
// neither symbols nor mappers so they're ultimately ordered by unstable type IDs, but given their rarity
|
|
// this should be fine.
|
|
if c := int(t1.objectFlags&ObjectFlagsObjectTypeKindMask) - int(t2.objectFlags&ObjectFlagsObjectTypeKindMask); c != 0 {
|
|
return c
|
|
}
|
|
if c := compareTypeMappers(t1.AsObjectType().mapper, t2.AsObjectType().mapper); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
case t1.flags&TypeFlagsUnion != 0:
|
|
// Unions are ordered by origin and then constituent type lists.
|
|
o1 := t1.AsUnionType().origin
|
|
o2 := t2.AsUnionType().origin
|
|
if o1 == nil && o2 == nil {
|
|
if c := compareTypeLists(t1.Types(), t2.Types()); c != 0 {
|
|
return c
|
|
}
|
|
} else if o1 == nil {
|
|
return 1
|
|
} else if o2 == nil {
|
|
return -1
|
|
} else {
|
|
if c := CompareTypes(o1, o2); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
case t1.flags&TypeFlagsIntersection != 0:
|
|
// Intersections are ordered by their constituent type lists.
|
|
if c := compareTypeLists(t1.Types(), t2.Types()); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&(TypeFlagsEnum|TypeFlagsEnumLiteral|TypeFlagsUniqueESSymbol) != 0:
|
|
// Enum members are ordered by their symbol (and thus their declaration order).
|
|
if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsStringLiteral != 0:
|
|
// String literal types are ordered by their values.
|
|
if c := strings.Compare(t1.AsLiteralType().value.(string), t2.AsLiteralType().value.(string)); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsNumberLiteral != 0:
|
|
// Numeric literal types are ordered by their values.
|
|
if c := cmp.Compare(t1.AsLiteralType().value.(jsnum.Number), t2.AsLiteralType().value.(jsnum.Number)); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsBooleanLiteral != 0:
|
|
b1 := t1.AsLiteralType().value.(bool)
|
|
b2 := t2.AsLiteralType().value.(bool)
|
|
if b1 != b2 {
|
|
if b1 {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
case t1.flags&TypeFlagsTypeParameter != 0:
|
|
if c := t1.checker.compareSymbols(t1.symbol, t2.symbol); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsIndex != 0:
|
|
if c := CompareTypes(t1.AsIndexType().target, t2.AsIndexType().target); c != 0 {
|
|
return c
|
|
}
|
|
if c := int(t1.AsIndexType().indexFlags) - int(t2.AsIndexType().indexFlags); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsIndexedAccess != 0:
|
|
if c := CompareTypes(t1.AsIndexedAccessType().objectType, t2.AsIndexedAccessType().objectType); c != 0 {
|
|
return c
|
|
}
|
|
if c := CompareTypes(t1.AsIndexedAccessType().indexType, t2.AsIndexedAccessType().indexType); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsConditional != 0:
|
|
if c := t1.checker.compareNodes(t1.AsConditionalType().root.node.AsNode(), t2.AsConditionalType().root.node.AsNode()); c != 0 {
|
|
return c
|
|
}
|
|
if c := compareTypeMappers(t1.AsConditionalType().mapper, t2.AsConditionalType().mapper); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsSubstitution != 0:
|
|
if c := CompareTypes(t1.AsSubstitutionType().baseType, t2.AsSubstitutionType().baseType); c != 0 {
|
|
return c
|
|
}
|
|
if c := CompareTypes(t1.AsSubstitutionType().constraint, t2.AsSubstitutionType().constraint); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsTemplateLiteral != 0:
|
|
if c := slices.Compare(t1.AsTemplateLiteralType().texts, t2.AsTemplateLiteralType().texts); c != 0 {
|
|
return c
|
|
}
|
|
if c := compareTypeLists(t1.AsTemplateLiteralType().types, t2.AsTemplateLiteralType().types); c != 0 {
|
|
return c
|
|
}
|
|
case t1.flags&TypeFlagsStringMapping != 0:
|
|
if c := CompareTypes(t1.AsStringMappingType().target, t2.AsStringMappingType().target); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
// Fall back to type IDs. This results in type creation order for built-in types.
|
|
return int(t1.id) - int(t2.id)
|
|
}
|
|
|
|
func getSortOrderFlags(t *Type) int {
|
|
// Return TypeFlagsEnum for all enum-like unit types (they'll be sorted by their symbols)
|
|
if t.flags&(TypeFlagsEnumLiteral|TypeFlagsEnum) != 0 && t.flags&TypeFlagsUnion == 0 {
|
|
return int(TypeFlagsEnum)
|
|
}
|
|
return int(t.flags)
|
|
}
|
|
|
|
func compareTypeNames(t1, t2 *Type) int {
|
|
s1 := getTypeNameSymbol(t1)
|
|
s2 := getTypeNameSymbol(t2)
|
|
if s1 == s2 {
|
|
if t1.alias != nil {
|
|
return compareTypeLists(t1.alias.typeArguments, t2.alias.typeArguments)
|
|
}
|
|
return 0
|
|
}
|
|
if s1 == nil {
|
|
return 1
|
|
}
|
|
if s2 == nil {
|
|
return -1
|
|
}
|
|
return strings.Compare(s1.Name, s2.Name)
|
|
}
|
|
|
|
func getTypeNameSymbol(t *Type) *ast.Symbol {
|
|
if t.alias != nil {
|
|
return t.alias.symbol
|
|
}
|
|
if t.flags&(TypeFlagsTypeParameter|TypeFlagsStringMapping) != 0 || t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 {
|
|
return t.symbol
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getObjectTypeName(t *Type) *ast.Symbol {
|
|
if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 {
|
|
return t.symbol
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func compareTupleTypes(t1, t2 *TupleType) int {
|
|
if t1 == t2 {
|
|
return 0
|
|
}
|
|
if t1.readonly != t2.readonly {
|
|
return core.IfElse(t1.readonly, 1, -1)
|
|
}
|
|
if len(t1.elementInfos) != len(t2.elementInfos) {
|
|
return len(t1.elementInfos) - len(t2.elementInfos)
|
|
}
|
|
for i := range t1.elementInfos {
|
|
if c := int(t1.elementInfos[i].flags) - int(t2.elementInfos[i].flags); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
for i := range t1.elementInfos {
|
|
if c := compareElementLabels(t1.elementInfos[i].labeledDeclaration, t2.elementInfos[i].labeledDeclaration); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func compareElementLabels(n1, n2 *ast.Node) int {
|
|
if n1 == n2 {
|
|
return 0
|
|
}
|
|
if n1 == nil {
|
|
return -1
|
|
}
|
|
if n2 == nil {
|
|
return 1
|
|
}
|
|
return strings.Compare(n1.Name().Text(), n2.Name().Text())
|
|
}
|
|
|
|
func compareTypeLists(s1, s2 []*Type) int {
|
|
if len(s1) != len(s2) {
|
|
return len(s1) - len(s2)
|
|
}
|
|
for i, t1 := range s1 {
|
|
if c := CompareTypes(t1, s2[i]); c != 0 {
|
|
return c
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func compareTypeMappers(m1, m2 *TypeMapper) int {
|
|
if m1 == m2 {
|
|
return 0
|
|
}
|
|
if m1 == nil {
|
|
return 1
|
|
}
|
|
if m2 == nil {
|
|
return -1
|
|
}
|
|
kind1 := m1.Kind()
|
|
kind2 := m2.Kind()
|
|
if kind1 != kind2 {
|
|
return int(kind1) - int(kind2)
|
|
}
|
|
switch kind1 {
|
|
case TypeMapperKindSimple:
|
|
m1 := m1.data.(*SimpleTypeMapper)
|
|
m2 := m2.data.(*SimpleTypeMapper)
|
|
if c := CompareTypes(m1.source, m2.source); c != 0 {
|
|
return c
|
|
}
|
|
return CompareTypes(m1.target, m2.target)
|
|
case TypeMapperKindArray:
|
|
m1 := m1.data.(*ArrayTypeMapper)
|
|
m2 := m2.data.(*ArrayTypeMapper)
|
|
if c := compareTypeLists(m1.sources, m2.sources); c != 0 {
|
|
return c
|
|
}
|
|
return compareTypeLists(m1.targets, m2.targets)
|
|
case TypeMapperKindMerged:
|
|
m1 := m1.data.(*MergedTypeMapper)
|
|
m2 := m2.data.(*MergedTypeMapper)
|
|
if c := compareTypeMappers(m1.m1, m2.m1); c != 0 {
|
|
return c
|
|
}
|
|
return compareTypeMappers(m1.m2, m2.m2)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func getDeclarationModifierFlagsFromSymbol(s *ast.Symbol) ast.ModifierFlags {
|
|
return getDeclarationModifierFlagsFromSymbolEx(s, false /*isWrite*/)
|
|
}
|
|
|
|
func getDeclarationModifierFlagsFromSymbolEx(s *ast.Symbol, isWrite bool) ast.ModifierFlags {
|
|
if s.ValueDeclaration != nil {
|
|
var declaration *ast.Node
|
|
if isWrite {
|
|
declaration = core.Find(s.Declarations, ast.IsSetAccessorDeclaration)
|
|
}
|
|
if declaration == nil && s.Flags&ast.SymbolFlagsGetAccessor != 0 {
|
|
declaration = core.Find(s.Declarations, ast.IsGetAccessorDeclaration)
|
|
}
|
|
if declaration == nil {
|
|
declaration = s.ValueDeclaration
|
|
}
|
|
flags := ast.GetCombinedModifierFlags(declaration)
|
|
if s.Parent != nil && s.Parent.Flags&ast.SymbolFlagsClass != 0 {
|
|
return flags
|
|
}
|
|
return flags & ^ast.ModifierFlagsAccessibilityModifier
|
|
}
|
|
if s.CheckFlags&ast.CheckFlagsSynthetic != 0 {
|
|
var accessModifier ast.ModifierFlags
|
|
switch {
|
|
case s.CheckFlags&ast.CheckFlagsContainsPrivate != 0:
|
|
accessModifier = ast.ModifierFlagsPrivate
|
|
case s.CheckFlags&ast.CheckFlagsContainsPublic != 0:
|
|
accessModifier = ast.ModifierFlagsPublic
|
|
default:
|
|
accessModifier = ast.ModifierFlagsProtected
|
|
}
|
|
var staticModifier ast.ModifierFlags
|
|
if s.CheckFlags&ast.CheckFlagsContainsStatic != 0 {
|
|
staticModifier = ast.ModifierFlagsStatic
|
|
}
|
|
return accessModifier | staticModifier
|
|
}
|
|
if s.Flags&ast.SymbolFlagsPrototype != 0 {
|
|
return ast.ModifierFlagsPublic | ast.ModifierFlagsStatic
|
|
}
|
|
return ast.ModifierFlagsNone
|
|
}
|
|
|
|
func isExponentiationOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindAsteriskAsteriskToken
|
|
}
|
|
|
|
func isMultiplicativeOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindAsteriskToken || kind == ast.KindSlashToken || kind == ast.KindPercentToken
|
|
}
|
|
|
|
func isMultiplicativeOperatorOrHigher(kind ast.Kind) bool {
|
|
return isExponentiationOperator(kind) || isMultiplicativeOperator(kind)
|
|
}
|
|
|
|
func isAdditiveOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindPlusToken || kind == ast.KindMinusToken
|
|
}
|
|
|
|
func isAdditiveOperatorOrHigher(kind ast.Kind) bool {
|
|
return isAdditiveOperator(kind) || isMultiplicativeOperatorOrHigher(kind)
|
|
}
|
|
|
|
func isShiftOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindLessThanLessThanToken || kind == ast.KindGreaterThanGreaterThanToken ||
|
|
kind == ast.KindGreaterThanGreaterThanGreaterThanToken
|
|
}
|
|
|
|
func isShiftOperatorOrHigher(kind ast.Kind) bool {
|
|
return isShiftOperator(kind) || isAdditiveOperatorOrHigher(kind)
|
|
}
|
|
|
|
func isRelationalOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindLessThanToken || kind == ast.KindLessThanEqualsToken || kind == ast.KindGreaterThanToken ||
|
|
kind == ast.KindGreaterThanEqualsToken || kind == ast.KindInstanceOfKeyword || kind == ast.KindInKeyword
|
|
}
|
|
|
|
func isRelationalOperatorOrHigher(kind ast.Kind) bool {
|
|
return isRelationalOperator(kind) || isShiftOperatorOrHigher(kind)
|
|
}
|
|
|
|
func isEqualityOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindEqualsEqualsToken || kind == ast.KindEqualsEqualsEqualsToken ||
|
|
kind == ast.KindExclamationEqualsToken || kind == ast.KindExclamationEqualsEqualsToken
|
|
}
|
|
|
|
func isEqualityOperatorOrHigher(kind ast.Kind) bool {
|
|
return isEqualityOperator(kind) || isRelationalOperatorOrHigher(kind)
|
|
}
|
|
|
|
func isBitwiseOperator(kind ast.Kind) bool {
|
|
return kind == ast.KindAmpersandToken || kind == ast.KindBarToken || kind == ast.KindCaretToken
|
|
}
|
|
|
|
func isBitwiseOperatorOrHigher(kind ast.Kind) bool {
|
|
return isBitwiseOperator(kind) || isEqualityOperatorOrHigher(kind)
|
|
}
|
|
|
|
func isLogicalOperatorOrHigher(kind ast.Kind) bool {
|
|
return ast.IsLogicalBinaryOperator(kind) || isBitwiseOperatorOrHigher(kind)
|
|
}
|
|
|
|
func isAssignmentOperatorOrHigher(kind ast.Kind) bool {
|
|
return kind == ast.KindQuestionQuestionToken || isLogicalOperatorOrHigher(kind) || ast.IsAssignmentOperator(kind)
|
|
}
|
|
|
|
func isBinaryOperator(kind ast.Kind) bool {
|
|
return isAssignmentOperatorOrHigher(kind) || kind == ast.KindCommaToken
|
|
}
|
|
|
|
func isObjectLiteralType(t *Type) bool {
|
|
return t.objectFlags&ObjectFlagsObjectLiteral != 0
|
|
}
|
|
|
|
func isDeclarationReadonly(declaration *ast.Node) bool {
|
|
return ast.GetCombinedModifierFlags(declaration)&ast.ModifierFlagsReadonly != 0 && !ast.IsParameterPropertyDeclaration(declaration, declaration.Parent)
|
|
}
|
|
|
|
// orderedSetMapThreshold is the size at which an orderedSet materializes its dedup map.
|
|
// Below this, contains() scans the values slice.
|
|
const orderedSetMapThreshold = 16
|
|
|
|
type orderedSet[T comparable] struct {
|
|
valuesByKey map[T]struct{}
|
|
values []T
|
|
}
|
|
|
|
func (s *orderedSet[T]) contains(value T) bool {
|
|
if s.valuesByKey == nil {
|
|
return slices.Contains(s.values, value)
|
|
}
|
|
_, ok := s.valuesByKey[value]
|
|
return ok
|
|
}
|
|
|
|
func (s *orderedSet[T]) add(value T) {
|
|
s.values = append(s.values, value)
|
|
// Small sets are served by a linear scan over values; only materialize the map once the set
|
|
// grows large enough for hashing to win.
|
|
if s.valuesByKey == nil {
|
|
if len(s.values) <= orderedSetMapThreshold {
|
|
return
|
|
}
|
|
s.valuesByKey = make(map[T]struct{}, len(s.values))
|
|
for _, v := range s.values[:len(s.values)-1] {
|
|
s.valuesByKey[v] = struct{}{}
|
|
}
|
|
}
|
|
s.valuesByKey[value] = struct{}{}
|
|
}
|
|
|
|
func getContainingFunctionOrClassStaticBlock(node *ast.Node) *ast.Node {
|
|
return ast.FindAncestor(node.Parent, ast.IsFunctionLikeOrClassStaticBlockDeclaration)
|
|
}
|
|
|
|
func isNodeDescendantOf(node *ast.Node, ancestor *ast.Node) bool {
|
|
for node != nil {
|
|
if node == ancestor {
|
|
return true
|
|
}
|
|
node = node.Parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isTypeUsableAsPropertyName(t *Type) bool {
|
|
return t.flags&TypeFlagsStringOrNumberLiteralOrUnique != 0
|
|
}
|
|
|
|
/**
|
|
* Gets the symbolic name for a member from its type.
|
|
*/
|
|
func getPropertyNameFromType(t *Type) string {
|
|
switch {
|
|
case t.flags&TypeFlagsStringLiteral != 0:
|
|
return t.AsLiteralType().value.(string)
|
|
case t.flags&TypeFlagsNumberLiteral != 0:
|
|
return t.AsLiteralType().value.(jsnum.Number).String()
|
|
case t.flags&TypeFlagsUniqueESSymbol != 0:
|
|
return t.AsUniqueESSymbolType().name
|
|
}
|
|
panic("Unhandled case in getPropertyNameFromType")
|
|
}
|
|
|
|
func isNumericLiteralName(name string) bool {
|
|
// The intent of numeric names is that
|
|
// - they are names with text in a numeric form, and that
|
|
// - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit',
|
|
// acquired by applying the abstract 'ToNumber' operation on the name's text.
|
|
//
|
|
// The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name.
|
|
// In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold.
|
|
//
|
|
// Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)'
|
|
// according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'.
|
|
// Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names
|
|
// because their 'ToString' representation is not equal to their original text.
|
|
// This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1.
|
|
//
|
|
// Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'.
|
|
// The '+' prefix operator is equivalent here to applying the abstract ToNumber operation.
|
|
// Applying the 'toString()' method on a number gives us the abstract ToString operation on a number.
|
|
//
|
|
// Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional.
|
|
// This is desired behavior, because when indexing with them as numeric entities, you are indexing
|
|
// with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively.
|
|
return jsnum.FromString(name).String() == name
|
|
}
|
|
|
|
func isThisProperty(node *ast.Node) bool {
|
|
return (ast.IsPropertyAccessExpression(node) || ast.IsElementAccessExpression(node)) && node.Expression().Kind == ast.KindThisKeyword
|
|
}
|
|
|
|
func isValidNumberString(s string, roundTripOnly bool) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
n := jsnum.FromString(s)
|
|
return !n.IsNaN() && !n.IsInf() && (!roundTripOnly || n.String() == s)
|
|
}
|
|
|
|
func isValidBigIntString(s string, roundTripOnly bool) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
scanner := scanner.NewScanner()
|
|
scanner.SetSkipTrivia(false)
|
|
success := true
|
|
scanner.SetOnError(func(diagnostic *diagnostics.Message, start, length int, args ...any) {
|
|
success = false
|
|
})
|
|
scanner.SetText(s + "n")
|
|
result := scanner.Scan()
|
|
negative := result == ast.KindMinusToken
|
|
if negative {
|
|
result = scanner.Scan()
|
|
}
|
|
flags := scanner.TokenFlags()
|
|
// validate that
|
|
// * scanning proceeded without error
|
|
// * a bigint can be scanned, and that when it is scanned, it is
|
|
// * the full length of the input string (so the scanner is one character beyond the augmented input length)
|
|
// * it does not contain a numeric separator (the `BigInt` constructor does not accept a numeric separator in its input)
|
|
return success && result == ast.KindBigIntLiteral && scanner.TokenEnd() == len(s)+1 && flags&ast.TokenFlagsContainsSeparator == 0 &&
|
|
(!roundTripOnly || s == pseudoBigIntToString(jsnum.NewPseudoBigInt(jsnum.ParsePseudoBigInt(scanner.TokenValue()), negative)))
|
|
}
|
|
|
|
func isValidESSymbolDeclaration(node *ast.Node) bool {
|
|
if ast.IsVariableDeclaration(node) {
|
|
return ast.IsVarConst(node) && ast.IsIdentifier(node.AsVariableDeclaration().Name()) && isVariableDeclarationInVariableStatement(node)
|
|
}
|
|
if ast.IsPropertyDeclaration(node) {
|
|
return hasReadonlyModifier(node) && ast.HasStaticModifier(node)
|
|
}
|
|
return ast.IsPropertySignatureDeclaration(node) && hasReadonlyModifier(node)
|
|
}
|
|
|
|
func isVariableDeclarationInVariableStatement(node *ast.Node) bool {
|
|
return ast.IsVariableDeclarationList(node.Parent) && ast.IsVariableStatement(node.Parent.Parent)
|
|
}
|
|
|
|
func IsKnownSymbol(symbol *ast.Symbol) bool {
|
|
return isLateBoundName(symbol.Name)
|
|
}
|
|
|
|
func IsPrivateIdentifierSymbol(symbol *ast.Symbol) bool {
|
|
if symbol == nil {
|
|
return false
|
|
}
|
|
return strings.HasPrefix(symbol.Name, ast.InternalSymbolNamePrefix+"#")
|
|
}
|
|
|
|
func isLateBoundName(name string) bool {
|
|
return len(name) >= 2 && name[0] == '\xfe' && name[1] == '@'
|
|
}
|
|
|
|
func isObjectOrArrayLiteralType(t *Type) bool {
|
|
return t.objectFlags&(ObjectFlagsObjectLiteral|ObjectFlagsArrayLiteral) != 0
|
|
}
|
|
|
|
func getContainingClassExcludingClassDecorators(node *ast.Node) *ast.ClassLikeDeclaration {
|
|
decorator := ast.FindAncestorOrQuit(node.Parent, func(n *ast.Node) ast.FindAncestorResult {
|
|
if ast.IsClassLike(n) {
|
|
return ast.FindAncestorQuit
|
|
}
|
|
if ast.IsDecorator(n) {
|
|
return ast.FindAncestorTrue
|
|
}
|
|
return ast.FindAncestorFalse
|
|
})
|
|
if decorator != nil && ast.IsClassLike(decorator.Parent) {
|
|
return ast.GetContainingClass(decorator.Parent)
|
|
}
|
|
if decorator != nil {
|
|
return ast.GetContainingClass(decorator)
|
|
}
|
|
return ast.GetContainingClass(node)
|
|
}
|
|
|
|
func isThisTypeParameter(t *Type) bool {
|
|
return t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isThisType
|
|
}
|
|
|
|
func isClassInstanceProperty(node *ast.Node) bool {
|
|
if ast.IsInJSFile(node) && ast.IsExpandoPropertyDeclaration(node) {
|
|
left := node.AsBinaryExpression().Left
|
|
return (!ast.IsBindableStaticAccessExpression(left, false /*excludeThisKeyword*/) || !ast.IsPrototypeAccess(left.Expression())) &&
|
|
!ast.IsBindableStaticNameExpression(left, true /*excludeThisKeyword*/)
|
|
}
|
|
return node.Parent != nil && ast.IsClassLike(node.Parent) && ast.IsPropertyDeclaration(node) && !ast.HasAccessorModifier(node)
|
|
}
|
|
|
|
func isThisInitializedObjectBindingExpression(node *ast.Node) bool {
|
|
return node != nil && (ast.IsShorthandPropertyAssignment(node) || ast.IsPropertyAssignment(node)) && ast.IsBinaryExpression(node.Parent.Parent) &&
|
|
node.Parent.Parent.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken &&
|
|
node.Parent.Parent.AsBinaryExpression().Right.Kind == ast.KindThisKeyword
|
|
}
|
|
|
|
func isThisInitializedDeclaration(node *ast.Node) bool {
|
|
return node != nil && ast.IsVariableDeclaration(node) && node.Initializer() != nil && node.Initializer().Kind == ast.KindThisKeyword
|
|
}
|
|
|
|
func isInfinityOrNaNString(name string) bool {
|
|
return name == "Infinity" || name == "-Infinity" || name == "NaN"
|
|
}
|
|
|
|
func (c *Checker) isConstantVariable(symbol *ast.Symbol) bool {
|
|
return symbol.Flags&ast.SymbolFlagsVariable != 0 && (c.getDeclarationNodeFlagsFromSymbol(symbol)&ast.NodeFlagsConstant) != 0
|
|
}
|
|
|
|
func (c *Checker) isParameterOrMutableLocalVariable(symbol *ast.Symbol) bool {
|
|
// Return true if symbol is a parameter, a catch clause variable, or a mutable local variable
|
|
if symbol.ValueDeclaration != nil {
|
|
declaration := ast.GetRootDeclaration(symbol.ValueDeclaration)
|
|
return declaration != nil && (ast.IsParameterDeclaration(declaration) || ast.IsVariableDeclaration(declaration) && (ast.IsCatchClause(declaration.Parent) || c.isMutableLocalVariableDeclaration(declaration)))
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Checker) isMutableLocalVariableDeclaration(declaration *ast.Node) bool {
|
|
// Return true if symbol is a non-exported and non-global `let` variable
|
|
return declaration.Parent.Flags&ast.NodeFlagsLet != 0 && !(ast.GetCombinedModifierFlags(declaration)&ast.ModifierFlagsExport != 0 || declaration.Parent.Parent.Kind == ast.KindVariableStatement && ast.IsGlobalSourceFile(declaration.Parent.Parent.Parent))
|
|
}
|
|
|
|
func isInAmbientOrTypeNode(node *ast.Node) bool {
|
|
return node.Flags&ast.NodeFlagsAmbient != 0 || ast.FindAncestor(node, func(n *ast.Node) bool {
|
|
return ast.IsInterfaceDeclaration(n) || ast.IsTypeOrJSTypeAliasDeclaration(n) || ast.IsTypeLiteralNode(n)
|
|
}) != nil
|
|
}
|
|
|
|
func isLiteralExpressionOfObject(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression, ast.KindRegularExpressionLiteral,
|
|
ast.KindFunctionExpression, ast.KindClassExpression:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func canHaveFlowNode(node *ast.Node) bool {
|
|
return node.FlowNodeData() != nil
|
|
}
|
|
|
|
func isNonNullAccess(node *ast.Node) bool {
|
|
return ast.IsAccessExpression(node) && ast.IsNonNullExpression(node.Expression())
|
|
}
|
|
|
|
func getBindingElementPropertyName(node *ast.Node) *ast.Node {
|
|
return node.PropertyNameOrName()
|
|
}
|
|
|
|
func isCallChain(node *ast.Node) bool {
|
|
return ast.IsCallExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0
|
|
}
|
|
|
|
func (c *Checker) callLikeExpressionMayHaveTypeArguments(node *ast.Node) bool {
|
|
return ast.IsCallOrNewExpression(node) || ast.IsTaggedTemplateExpression(node) || ast.IsJsxOpeningLikeElement(node)
|
|
}
|
|
|
|
func isSuperCall(n *ast.Node) bool {
|
|
return ast.IsCallExpression(n) && n.Expression().Kind == ast.KindSuperKeyword
|
|
}
|
|
|
|
func getMembersOfDeclaration(node *ast.Node) []*ast.Node {
|
|
switch node.Kind {
|
|
case ast.KindInterfaceDeclaration, ast.KindClassDeclaration, ast.KindClassExpression, ast.KindTypeLiteral:
|
|
return node.Members()
|
|
case ast.KindObjectLiteralExpression:
|
|
return node.Properties()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isInRightSideOfImportOrExportAssignment(node *ast.EntityName) bool {
|
|
for node.Parent.Kind == ast.KindQualifiedName {
|
|
node = node.Parent
|
|
}
|
|
|
|
return node.Parent.Kind == ast.KindImportEqualsDeclaration && node.Parent.AsImportEqualsDeclaration().ModuleReference == node ||
|
|
node.Parent.Kind == ast.KindExportAssignment && node.Parent.Expression() == node
|
|
}
|
|
|
|
func isJsxIntrinsicTagName(tagName *ast.Node) bool {
|
|
return ast.IsIdentifier(tagName) && scanner.IsIntrinsicJsxName(tagName.Text()) || ast.IsJsxNamespacedName(tagName)
|
|
}
|
|
|
|
func getContainingObjectLiteral(f *ast.SignatureDeclaration) *ast.Node {
|
|
if (f.Kind == ast.KindMethodDeclaration ||
|
|
f.Kind == ast.KindGetAccessor ||
|
|
f.Kind == ast.KindSetAccessor) && f.Parent.Kind == ast.KindObjectLiteralExpression {
|
|
return f.Parent
|
|
} else if f.Kind == ast.KindFunctionExpression && f.Parent.Kind == ast.KindPropertyAssignment {
|
|
return f.Parent.Parent
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isImportTypeQualifierPart(node *ast.Node) *ast.Node {
|
|
parent := node.Parent
|
|
for ast.IsQualifiedName(parent) {
|
|
node = parent
|
|
parent = parent.Parent
|
|
}
|
|
|
|
if parent != nil && parent.Kind == ast.KindImportType && parent.AsImportTypeNode().Qualifier == node {
|
|
return parent
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func isInNameOfExpressionWithTypeArguments(node *ast.Node) bool {
|
|
for node.Parent.Kind == ast.KindPropertyAccessExpression {
|
|
node = node.Parent
|
|
}
|
|
|
|
return node.Parent.Kind == ast.KindExpressionWithTypeArguments
|
|
}
|
|
|
|
func getIndexSymbolFromSymbolTable(symbolTable ast.SymbolTable) *ast.Symbol {
|
|
return symbolTable[ast.InternalSymbolNameIndex]
|
|
}
|
|
|
|
// Indicates whether the result of an `Expression` will be unused.
|
|
// NOTE: This requires a node with a valid `parent` pointer.
|
|
func expressionResultIsUnused(node *ast.Node) bool {
|
|
for {
|
|
parent := node.Parent
|
|
// walk up parenthesized expressions, but keep a pointer to the top-most parenthesized expression
|
|
if ast.IsParenthesizedExpression(parent) {
|
|
node = parent
|
|
continue
|
|
}
|
|
// result is unused in an expression statement, `void` expression, or the initializer or incrementer of a `for` loop
|
|
if ast.IsExpressionStatement(parent) || ast.IsVoidExpression(parent) || ast.IsForStatement(parent) && (parent.Initializer() == node || parent.AsForStatement().Incrementor == node) {
|
|
return true
|
|
}
|
|
if ast.IsBinaryExpression(parent) && parent.AsBinaryExpression().OperatorToken.Kind == ast.KindCommaToken {
|
|
// left side of comma is always unused
|
|
if node == parent.AsBinaryExpression().Left {
|
|
return true
|
|
}
|
|
// right side of comma is unused if parent is unused
|
|
node = parent
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
func pseudoBigIntToString(value jsnum.PseudoBigInt) string {
|
|
return value.String()
|
|
}
|
|
|
|
func getSuperContainer(node *ast.Node, stopOnFunctions bool) *ast.Node {
|
|
for {
|
|
node = node.Parent
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
switch node.Kind {
|
|
case ast.KindComputedPropertyName:
|
|
node = node.Parent
|
|
case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction:
|
|
if !stopOnFunctions {
|
|
continue
|
|
}
|
|
fallthrough
|
|
case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor,
|
|
ast.KindGetAccessor, ast.KindSetAccessor, ast.KindClassStaticBlockDeclaration:
|
|
return node
|
|
case ast.KindDecorator:
|
|
// Decorators are always applied outside of the body of a class or method.
|
|
if ast.IsParameterDeclaration(node.Parent) && ast.IsClassElement(node.Parent.Parent) {
|
|
// If the decorator's parent is a Parameter, we resolve the this container from
|
|
// the grandparent class declaration.
|
|
node = node.Parent.Parent
|
|
} else if ast.IsClassElement(node.Parent) {
|
|
// If the decorator's parent is a class element, we resolve the 'this' container
|
|
// from the parent class declaration.
|
|
node = node.Parent
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func forEachYieldExpression(body *ast.Node, visitor func(expr *ast.Node) bool) bool {
|
|
var traverse func(*ast.Node) bool
|
|
traverse = func(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindYieldExpression:
|
|
if visitor(node) {
|
|
return true
|
|
}
|
|
operand := node.Expression()
|
|
if operand == nil {
|
|
return false
|
|
}
|
|
return traverse(operand)
|
|
case ast.KindEnumDeclaration, ast.KindInterfaceDeclaration, ast.KindModuleDeclaration, ast.KindTypeAliasDeclaration:
|
|
// These are not allowed inside a generator now, but eventually they may be allowed
|
|
// as local types. Regardless, skip them to avoid the work.
|
|
default:
|
|
if ast.IsFunctionLike(node) {
|
|
if node.Name() != nil && ast.IsComputedPropertyName(node.Name()) {
|
|
// Note that we will not include methods/accessors of a class because they would require
|
|
// first descending into the class. This is by design.
|
|
return traverse(node.Name().Expression())
|
|
}
|
|
} else if !ast.IsPartOfTypeNode(node) {
|
|
// This is the general case, which should include mostly expressions and statements.
|
|
// Also includes NodeArrays.
|
|
return node.ForEachChild(traverse)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
return traverse(body)
|
|
}
|
|
|
|
func getEnclosingContainer(node *ast.Node) *ast.Node {
|
|
return ast.FindAncestor(node.Parent, func(n *ast.Node) bool {
|
|
return binder.GetContainerFlags(n)&binder.ContainerFlagsIsContainer != 0
|
|
})
|
|
}
|
|
|
|
func getDeclarationsOfKind(symbol *ast.Symbol, kind ast.Kind) []*ast.Node {
|
|
return core.Filter(symbol.Declarations, func(d *ast.Node) bool { return d.Kind == kind })
|
|
}
|
|
|
|
func hasType(node *ast.Node) bool {
|
|
return node.Type() != nil
|
|
}
|
|
|
|
func getNonRestParameterCount(sig *Signature) int {
|
|
return len(sig.parameters) - core.IfElse(signatureHasRestParameter(sig), 1, 0)
|
|
}
|
|
|
|
func minAndMax[T any](slice []T, getValue func(value T) int) (int, int) {
|
|
var minValue, maxValue int
|
|
for i, element := range slice {
|
|
value := getValue(element)
|
|
if i == 0 {
|
|
minValue = value
|
|
maxValue = value
|
|
} else {
|
|
minValue = min(minValue, value)
|
|
maxValue = max(maxValue, value)
|
|
}
|
|
}
|
|
return minValue, maxValue
|
|
}
|
|
|
|
type FeatureMapEntry struct {
|
|
lib string
|
|
props []string
|
|
}
|
|
|
|
var getFeatureMap = sync.OnceValue(func() map[string][]FeatureMapEntry {
|
|
return map[string][]FeatureMapEntry{
|
|
"Array": {
|
|
{lib: "es2015", props: []string{"find", "findIndex", "fill", "copyWithin", "entries", "keys", "values"}},
|
|
{lib: "es2016", props: []string{"includes"}},
|
|
{lib: "es2019", props: []string{"flat", "flatMap"}},
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Iterator": {
|
|
{lib: "es2015", props: []string{}},
|
|
},
|
|
"AsyncIterator": {
|
|
{lib: "es2015", props: []string{}},
|
|
},
|
|
"ArrayBuffer": {
|
|
{lib: "es2024", props: []string{
|
|
"maxByteLength",
|
|
"resizable",
|
|
"resize",
|
|
"detached",
|
|
"transfer",
|
|
"transferToFixedLength",
|
|
}},
|
|
},
|
|
"Atomics": {
|
|
{lib: "es2017", props: []string{
|
|
"add",
|
|
"and",
|
|
"compareExchange",
|
|
"exchange",
|
|
"isLockFree",
|
|
"load",
|
|
"or",
|
|
"store",
|
|
"sub",
|
|
"wait",
|
|
"notify",
|
|
"xor",
|
|
}},
|
|
{lib: "es2024", props: []string{
|
|
"waitAsync",
|
|
}},
|
|
},
|
|
"SharedArrayBuffer": {
|
|
{lib: "es2017", props: []string{
|
|
"byteLength",
|
|
"slice",
|
|
}},
|
|
{lib: "es2024", props: []string{
|
|
"growable",
|
|
"maxByteLength",
|
|
"grow",
|
|
}},
|
|
},
|
|
"AsyncIterable": {
|
|
{lib: "es2018", props: []string{}},
|
|
},
|
|
"AsyncIterableIterator": {
|
|
{lib: "es2018", props: []string{}},
|
|
},
|
|
"AsyncGenerator": {
|
|
{lib: "es2018", props: []string{}},
|
|
},
|
|
"AsyncGeneratorFunction": {
|
|
{lib: "es2018", props: []string{}},
|
|
},
|
|
"RegExp": {
|
|
{lib: "es2015", props: []string{"flags", "sticky", "unicode"}},
|
|
{lib: "es2018", props: []string{"dotAll"}},
|
|
{lib: "es2024", props: []string{"unicodeSets"}},
|
|
},
|
|
"RegExpConstructor": {
|
|
{lib: "es2025", props: []string{"escape"}},
|
|
},
|
|
"Reflect": {
|
|
{lib: "es2015", props: []string{"apply", "construct", "defineProperty", "deleteProperty", "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf"}},
|
|
},
|
|
"ArrayConstructor": {
|
|
{lib: "es2015", props: []string{"from", "of"}},
|
|
{lib: "esnext", props: []string{"fromAsync"}},
|
|
},
|
|
"ObjectConstructor": {
|
|
{lib: "es2015", props: []string{"assign", "getOwnPropertySymbols", "keys", "is", "setPrototypeOf"}},
|
|
{lib: "es2017", props: []string{"values", "entries", "getOwnPropertyDescriptors"}},
|
|
{lib: "es2019", props: []string{"fromEntries"}},
|
|
{lib: "es2022", props: []string{"hasOwn"}},
|
|
{lib: "es2024", props: []string{"groupBy"}},
|
|
},
|
|
"NumberConstructor": {
|
|
{lib: "es2015", props: []string{"isFinite", "isInteger", "isNaN", "isSafeInteger", "parseFloat", "parseInt"}},
|
|
},
|
|
"Math": {
|
|
{lib: "es2015", props: []string{"clz32", "imul", "sign", "log10", "log2", "log1p", "expm1", "cosh", "sinh", "tanh", "acosh", "asinh", "atanh", "hypot", "trunc", "fround", "cbrt"}},
|
|
{lib: "es2025", props: []string{"f16round"}},
|
|
},
|
|
"Map": {
|
|
{lib: "es2015", props: []string{"entries", "keys", "values"}},
|
|
{lib: "esnext", props: []string{
|
|
"getOrInsert",
|
|
"getOrInsertComputed",
|
|
}},
|
|
},
|
|
"MapConstructor": {
|
|
{lib: "es2024", props: []string{"groupBy"}},
|
|
},
|
|
"Set": {
|
|
{lib: "es2015", props: []string{"entries", "keys", "values"}},
|
|
{lib: "es2025", props: []string{
|
|
"union",
|
|
"intersection",
|
|
"difference",
|
|
"symmetricDifference",
|
|
"isSubsetOf",
|
|
"isSupersetOf",
|
|
"isDisjointFrom",
|
|
}},
|
|
},
|
|
"PromiseConstructor": {
|
|
{lib: "es2015", props: []string{"all", "race", "reject", "resolve"}},
|
|
{lib: "es2020", props: []string{"allSettled"}},
|
|
{lib: "es2021", props: []string{"any"}},
|
|
{lib: "es2024", props: []string{"withResolvers"}},
|
|
{lib: "es2025", props: []string{"try"}},
|
|
},
|
|
"Symbol": {
|
|
{lib: "es2015", props: []string{"for", "keyFor"}},
|
|
{lib: "es2019", props: []string{"description"}},
|
|
},
|
|
"WeakMap": {
|
|
{lib: "es2015", props: []string{}},
|
|
{lib: "esnext", props: []string{
|
|
"getOrInsert",
|
|
"getOrInsertComputed",
|
|
}},
|
|
},
|
|
"WeakSet": {
|
|
{lib: "es2015", props: []string{}},
|
|
},
|
|
"String": {
|
|
{lib: "es2015", props: []string{"codePointAt", "includes", "endsWith", "normalize", "repeat", "startsWith", "anchor", "big", "blink", "bold", "fixed", "fontcolor", "fontsize", "italics", "link", "small", "strike", "sub", "sup"}},
|
|
{lib: "es2017", props: []string{"padStart", "padEnd"}},
|
|
{lib: "es2019", props: []string{"trimStart", "trimEnd", "trimLeft", "trimRight"}},
|
|
{lib: "es2020", props: []string{"matchAll"}},
|
|
{lib: "es2021", props: []string{"replaceAll"}},
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2024", props: []string{"isWellFormed", "toWellFormed"}},
|
|
},
|
|
"StringConstructor": {
|
|
{lib: "es2015", props: []string{"fromCodePoint", "raw"}},
|
|
},
|
|
"DateTimeFormat": {
|
|
{lib: "es2017", props: []string{"formatToParts"}},
|
|
},
|
|
"Promise": {
|
|
{lib: "es2015", props: []string{}},
|
|
{lib: "es2018", props: []string{"finally"}},
|
|
},
|
|
"RegExpMatchArray": {
|
|
{lib: "es2018", props: []string{"groups"}},
|
|
},
|
|
"RegExpExecArray": {
|
|
{lib: "es2018", props: []string{"groups"}},
|
|
},
|
|
"Intl": {
|
|
{lib: "es2018", props: []string{"PluralRules"}},
|
|
{lib: "es2020", props: []string{"RelativeTimeFormat", "Locale", "DisplayNames"}},
|
|
{lib: "es2021", props: []string{"ListFormat", "DateTimeFormat"}},
|
|
{lib: "es2022", props: []string{"Segmenter"}},
|
|
{lib: "es2025", props: []string{"DurationFormat"}},
|
|
},
|
|
"NumberFormat": {
|
|
{lib: "es2018", props: []string{"formatToParts"}},
|
|
},
|
|
"SymbolConstructor": {
|
|
{lib: "es2020", props: []string{"matchAll"}},
|
|
{lib: "esnext", props: []string{
|
|
"metadata",
|
|
"dispose",
|
|
"asyncDispose",
|
|
}},
|
|
},
|
|
"DataView": {
|
|
{lib: "es2020", props: []string{"setBigInt64", "setBigUint64", "getBigInt64", "getBigUint64"}},
|
|
{lib: "es2025", props: []string{"setFloat16", "getFloat16"}},
|
|
},
|
|
"BigInt": {
|
|
{lib: "es2020", props: []string{}},
|
|
},
|
|
"RelativeTimeFormat": {
|
|
{lib: "es2020", props: []string{"format", "formatToParts", "resolvedOptions"}},
|
|
},
|
|
"Int8Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Uint8Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Uint8ClampedArray": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Int16Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Uint16Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Int32Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Uint32Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Float16Array": {
|
|
{lib: "es2025", props: []string{}},
|
|
},
|
|
"Float32Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Float64Array": {
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"BigInt64Array": {
|
|
{lib: "es2020", props: []string{}},
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"BigUint64Array": {
|
|
{lib: "es2020", props: []string{}},
|
|
{lib: "es2022", props: []string{"at"}},
|
|
{lib: "es2023", props: []string{"findLastIndex", "findLast", "toReversed", "toSorted", "toSpliced", "with"}},
|
|
},
|
|
"Error": {
|
|
{lib: "es2022", props: []string{"cause"}},
|
|
},
|
|
"ErrorConstructor": {
|
|
{lib: "esnext", props: []string{"isError"}},
|
|
},
|
|
"Uint8ArrayConstructor": {
|
|
{lib: "esnext", props: []string{"fromBase64", "fromHex"}},
|
|
},
|
|
"DisposableStack": {
|
|
{lib: "esnext", props: []string{}},
|
|
},
|
|
"AsyncDisposableStack": {
|
|
{lib: "esnext", props: []string{}},
|
|
},
|
|
"Date": {
|
|
{lib: "esnext", props: []string{"toTemporalInstant"}},
|
|
},
|
|
}
|
|
})
|
|
|
|
func rangeOfTypeParameters(sourceFile *ast.SourceFile, typeParameters *ast.NodeList) core.TextRange {
|
|
return core.NewTextRange(typeParameters.Pos()-1, min(len(sourceFile.Text()), scanner.SkipTrivia(sourceFile.Text(), typeParameters.End())+1))
|
|
}
|
|
|
|
func tryGetPropertyAccessOrIdentifierToString(expr *ast.Node) string {
|
|
switch {
|
|
case ast.IsPropertyAccessExpression(expr):
|
|
baseStr := tryGetPropertyAccessOrIdentifierToString(expr.Expression())
|
|
if baseStr != "" {
|
|
return baseStr + "." + entityNameToString(expr.Name())
|
|
}
|
|
case ast.IsElementAccessExpression(expr):
|
|
baseStr := tryGetPropertyAccessOrIdentifierToString(expr.Expression())
|
|
if baseStr != "" && ast.IsPropertyName(expr.AsElementAccessExpression().ArgumentExpression) {
|
|
return baseStr + "." + ast.GetPropertyNameForPropertyNameNode(expr.AsElementAccessExpression().ArgumentExpression)
|
|
}
|
|
case ast.IsIdentifier(expr):
|
|
return expr.Text()
|
|
case ast.IsJsxNamespacedName(expr):
|
|
return entityNameToString(expr)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func allDeclarationsInSameSourceFile(symbol *ast.Symbol) bool {
|
|
if len(symbol.Declarations) > 1 {
|
|
var sourceFile *ast.SourceFile
|
|
for i, d := range symbol.Declarations {
|
|
if i == 0 {
|
|
sourceFile = ast.GetSourceFileOfNode(d)
|
|
} else if ast.GetSourceFileOfNode(d) != sourceFile {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func containsNonMissingUndefinedType(c *Checker, t *Type) bool {
|
|
var candidate *Type
|
|
if t.flags&TypeFlagsUnion != 0 {
|
|
candidate = t.AsUnionType().types[0]
|
|
} else {
|
|
candidate = t
|
|
}
|
|
return candidate.flags&TypeFlagsUndefined != 0 && candidate != c.missingType
|
|
}
|
|
|
|
func getAnyImportSyntax(node *ast.Node) *ast.Node {
|
|
var importNode *ast.Node
|
|
switch node.Kind {
|
|
case ast.KindImportEqualsDeclaration:
|
|
importNode = node
|
|
case ast.KindImportClause:
|
|
importNode = node.Parent
|
|
case ast.KindNamespaceImport:
|
|
importNode = node.Parent.Parent
|
|
case ast.KindImportSpecifier:
|
|
importNode = node.Parent.Parent.Parent
|
|
default:
|
|
return nil
|
|
}
|
|
return importNode
|
|
}
|
|
|
|
// A reserved member name consists of the byte 0xFE (which is an invalid UTF-8 encoding) followed by one or more
|
|
// characters where the first character is not '@' or '#'. The '@' character indicates that the name is denoted by
|
|
// a well known ES Symbol instance and the '#' character indicates that the name is a PrivateIdentifier.
|
|
func isReservedMemberName(name string) bool {
|
|
return len(name) >= 2 && name[0] == '\xFE' && name[1] != '@' && name[1] != '#'
|
|
}
|
|
|
|
func introducesArgumentsExoticObject(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor,
|
|
ast.KindSetAccessor, ast.KindFunctionDeclaration, ast.KindFunctionExpression:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func symbolsToArray(symbols ast.SymbolTable) []*ast.Symbol {
|
|
var result []*ast.Symbol
|
|
for id, symbol := range symbols {
|
|
if !isReservedMemberName(id) {
|
|
result = append(result, symbol)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func SkipAlias(symbol *ast.Symbol, checker *Checker) *ast.Symbol {
|
|
if symbol.Flags&ast.SymbolFlagsAlias != 0 {
|
|
return checker.GetAliasedSymbol(symbol)
|
|
}
|
|
return symbol
|
|
}
|
|
|
|
// True if the symbol is for an external module, as opposed to a namespace.
|
|
func IsExternalModuleSymbol(moduleSymbol *ast.Symbol) bool {
|
|
firstRune, _ := utf8.DecodeRuneInString(moduleSymbol.Name)
|
|
return moduleSymbol.Flags&ast.SymbolFlagsModule != 0 && firstRune == '"'
|
|
}
|
|
|
|
func (c *Checker) isCanceled() bool {
|
|
return c.ctx != nil && c.ctx.Err() != nil
|
|
}
|
|
|
|
func (c *Checker) checkNotCanceled() {
|
|
if c.wasCanceled {
|
|
panic("Checker was previously cancelled")
|
|
}
|
|
}
|
|
|
|
func (c *Checker) getPackagesMap() map[string]bool {
|
|
if c.packagesMap == nil {
|
|
c.packagesMap = make(map[string]bool)
|
|
resolvedModules := c.program.GetResolvedModules()
|
|
for _, resolvedModulesInFile := range resolvedModules {
|
|
for _, module := range resolvedModulesInFile {
|
|
if module.PackageId.Name != "" {
|
|
c.packagesMap[module.PackageId.Name] = c.packagesMap[module.PackageId.Name] || module.Extension == tspath.ExtensionDts
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return c.packagesMap
|
|
}
|
|
|
|
func (c *Checker) typesPackageExists(packageName string) bool {
|
|
packagesMap := c.getPackagesMap()
|
|
_, ok := packagesMap[module.GetTypesPackageName(packageName)]
|
|
return ok
|
|
}
|
|
|
|
func (c *Checker) packageBundlesTypes(packageName string) bool {
|
|
packagesMap := c.getPackagesMap()
|
|
hasTypes, _ := packagesMap[packageName]
|
|
return hasTypes
|
|
}
|
|
|
|
func ValueToString(value any) string {
|
|
switch value := value.(type) {
|
|
case string:
|
|
return "\"" + printer.EscapeString(value, '"') + "\""
|
|
case jsnum.Number:
|
|
return value.String()
|
|
case bool:
|
|
return core.IfElse(value, "true", "false")
|
|
case jsnum.PseudoBigInt:
|
|
return value.String() + "n"
|
|
}
|
|
panic("unhandled value type in valueToString")
|
|
}
|
|
|
|
func nodeStartsNewLexicalEnvironment(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindConstructor, ast.KindFunctionExpression, ast.KindFunctionDeclaration, ast.KindArrowFunction,
|
|
ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindModuleDeclaration, ast.KindSourceFile:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Determines whether a did-you-mean error should be a suggestion in an unchecked JS file.
|
|
// Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck
|
|
// It does not suggest when the suggestion:
|
|
// - Is from a global file that is different from the reference file, or
|
|
// - (optionally) Is a class, or is a this.x property access expression
|
|
func (c *Checker) isUncheckedJSSuggestion(node *ast.Node, suggestion *ast.Symbol, excludeClasses bool) bool {
|
|
file := ast.GetSourceFileOfNode(node)
|
|
if file != nil {
|
|
if c.compilerOptions.CheckJs.IsUnknown() && file.CheckJsDirective == nil && (file.ScriptKind == core.ScriptKindJS || file.ScriptKind == core.ScriptKindJSX) {
|
|
var declarationFile *ast.SourceFile
|
|
if suggestion != nil {
|
|
if firstDeclaration := core.FirstOrNil(suggestion.Declarations); firstDeclaration != nil {
|
|
declarationFile = ast.GetSourceFileOfNode(firstDeclaration)
|
|
}
|
|
}
|
|
suggestionHasNoExtendsOrDecorators := suggestion == nil ||
|
|
suggestion.ValueDeclaration == nil ||
|
|
!ast.IsClassLike(suggestion.ValueDeclaration) ||
|
|
len(ast.GetExtendsHeritageClauseElements(suggestion.ValueDeclaration)) != 0 ||
|
|
ast.ClassOrConstructorParameterIsDecorated(false, suggestion.ValueDeclaration)
|
|
return !(file != declarationFile && declarationFile != nil && ast.IsGlobalSourceFile(declarationFile.AsNode())) &&
|
|
!(excludeClasses && suggestion != nil && suggestion.Flags&ast.SymbolFlagsClass != 0 && suggestionHasNoExtendsOrDecorators) &&
|
|
!(node != nil && excludeClasses && ast.IsPropertyAccessExpression(node) && node.Expression().Kind == ast.KindThisKeyword && suggestionHasNoExtendsOrDecorators)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Returns if a type is or consists of a JSLiteral object type
|
|
// In addition to objects which are directly literals,
|
|
// * unions where every element is a jsliteral
|
|
// * intersections where at least one element is a jsliteral
|
|
// * and instantiable types constrained to a jsliteral
|
|
// Should all count as literals and not print errors on access or assignment of possibly existing properties.
|
|
// This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference).
|
|
func (c *Checker) isJSLiteralType(t *Type) bool {
|
|
if c.noImplicitAny {
|
|
return false
|
|
// Flag is meaningless under `noImplicitAny` mode
|
|
}
|
|
if t.objectFlags&ObjectFlagsJSLiteral != 0 {
|
|
return true
|
|
}
|
|
if t.flags&TypeFlagsUnion != 0 {
|
|
return core.Every(t.AsUnionType().types, c.isJSLiteralType)
|
|
}
|
|
if t.flags&TypeFlagsIntersection != 0 {
|
|
return core.Some(t.AsIntersectionType().types, c.isJSLiteralType)
|
|
}
|
|
if t.flags&TypeFlagsInstantiable != 0 {
|
|
constraint := c.getResolvedBaseConstraint(t, nil)
|
|
return constraint != t && c.isJSLiteralType(constraint)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DiagnosticDetails holds a resolved diagnostic message and its arguments,
|
|
// used for sharing diagnostic chain computation between the checker and incremental builder.
|
|
type DiagnosticDetails struct {
|
|
Message *diagnostics.Message
|
|
Args []any
|
|
}
|
|
|
|
// CreateModuleNotFoundChain computes the diagnostic message and arguments for a module-not-found
|
|
// error chain entry. This is shared between the checker (initial diagnostic creation) and the
|
|
// incremental builder (repopulation of cached diagnostics).
|
|
// Mirrors createModuleNotFoundChain in the TypeScript compiler's utilities.ts.
|
|
func CreateModuleNotFoundChain(program Program, file *ast.SourceFile, moduleReference string, mode core.ResolutionMode, packageName string) DiagnosticDetails {
|
|
resolvedModule := program.GetResolvedModule(file, moduleReference, mode)
|
|
|
|
if resolvedModule != nil && resolvedModule.AlternateResult != "" {
|
|
if strings.Contains(resolvedModule.AlternateResult, "/node_modules/@types/") {
|
|
packageName = "@types/" + module.MangleScopedPackageName(packageName)
|
|
}
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_when_respecting_package_json_exports_The_1_library_may_need_to_update_its_package_json_or_typings,
|
|
Args: []any{resolvedModule.AlternateResult, packageName},
|
|
}
|
|
}
|
|
|
|
packagesMap := program.GetPackagesMap()
|
|
if _, ok := packagesMap[module.GetTypesPackageName(packageName)]; ok {
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
|
|
Args: []any{packageName, module.MangleScopedPackageName(packageName)},
|
|
}
|
|
}
|
|
if packagesMap[packageName] {
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
|
|
Args: []any{packageName, moduleReference},
|
|
}
|
|
}
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
|
|
Args: []any{moduleReference, module.MangleScopedPackageName(packageName)},
|
|
}
|
|
}
|
|
|
|
// CreateModeMismatchDetails computes the diagnostic message and arguments for a mode-mismatch
|
|
// error chain entry. This is shared between the checker (initial diagnostic creation) and the
|
|
// incremental builder (repopulation of cached diagnostics).
|
|
// Mirrors createModeMismatchDetails in the TypeScript compiler's utilities.ts.
|
|
func CreateModeMismatchDetails(program Program, file *ast.SourceFile) DiagnosticDetails {
|
|
ext := tspath.TryGetExtensionFromPath(file.FileName())
|
|
targetExt := core.IfElse(ext == tspath.ExtensionTs, tspath.ExtensionMts, core.IfElse(ext == tspath.ExtensionJs, tspath.ExtensionMjs, ""))
|
|
meta := program.GetSourceFileMetaData(file.Path())
|
|
packageJsonType := meta.PackageJsonType
|
|
packageJsonDirectory := meta.PackageJsonDirectory
|
|
|
|
if packageJsonDirectory != "" && packageJsonType == "" {
|
|
if targetExt != "" {
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.To_convert_this_file_to_an_ECMAScript_module_change_its_file_extension_to_0_or_add_the_field_type_Colon_module_to_1,
|
|
Args: []any{targetExt, tspath.CombinePaths(packageJsonDirectory, "package.json")},
|
|
}
|
|
}
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.To_convert_this_file_to_an_ECMAScript_module_add_the_field_type_Colon_module_to_0,
|
|
Args: []any{tspath.CombinePaths(packageJsonDirectory, "package.json")},
|
|
}
|
|
}
|
|
if targetExt != "" {
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.To_convert_this_file_to_an_ECMAScript_module_change_its_file_extension_to_0_or_create_a_local_package_json_file_with_type_Colon_module,
|
|
Args: []any{targetExt},
|
|
}
|
|
}
|
|
return DiagnosticDetails{
|
|
Message: diagnostics.To_convert_this_file_to_an_ECMAScript_module_create_a_local_package_json_file_with_type_Colon_module,
|
|
Args: nil,
|
|
}
|
|
}
|
|
|
|
func walkUpOuterExpressions(node *ast.Node) *ast.Node {
|
|
parent := node.Parent
|
|
for parent != nil && ast.IsOuterExpression(parent, ast.OEKAll) {
|
|
parent = parent.Parent
|
|
}
|
|
return parent
|
|
}
|
|
|
|
func GetSetAccessorValueParameter(accessor *ast.Node) *ast.Node {
|
|
parameters := accessor.Parameters()
|
|
if len(parameters) > 0 {
|
|
hasThis := len(parameters) == 2 && ast.IsThisParameter(parameters[0])
|
|
return parameters[core.IfElse(hasThis, 1, 0)]
|
|
}
|
|
return nil
|
|
}
|