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
9 changes: 9 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3921,3 +3921,12 @@ func HasContextSensitiveParameters(node *Node) bool {
func IsInfinityOrNaNString(name string) bool {
return name == "Infinity" || name == "-Infinity" || name == "NaN"
}

func GetFirstConstructorWithBody(node *Node) *Node {
for _, member := range node.Members() {
if IsConstructorDeclaration(member) && NodeIsPresent(member.Body()) {
return member
}
}
return nil
}
44 changes: 32 additions & 12 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1518,12 +1518,14 @@ func (c *Checker) onFailedToResolveSymbol(errorLocation *ast.Node, name string,
suggestion := c.getSuggestedSymbolForNonexistentSymbol(errorLocation, name, meaning)
if suggestion != nil && !(suggestion.ValueDeclaration != nil && ast.IsAmbientModule(suggestion.ValueDeclaration) && ast.IsGlobalScopeAugmentation(suggestion.ValueDeclaration)) {
suggestionName := c.symbolToString(suggestion)
message := core.IfElse(meaning == ast.SymbolFlagsNamespace, diagnostics.Cannot_find_namespace_0_Did_you_mean_1, diagnostics.Cannot_find_name_0_Did_you_mean_1)
isUncheckedJS := c.isUncheckedJSSuggestion(errorLocation, suggestion, false /*excludeClasses*/)
message := core.IfElse(meaning == ast.SymbolFlagsNamespace, diagnostics.Cannot_find_namespace_0_Did_you_mean_1,
core.IfElse(isUncheckedJS, diagnostics.Could_not_find_name_0_Did_you_mean_1, diagnostics.Cannot_find_name_0_Did_you_mean_1))
diagnostic := NewDiagnosticForNode(errorLocation, message, name, suggestionName)
if suggestion.ValueDeclaration != nil {
diagnostic.AddRelatedInfo(NewDiagnosticForNode(suggestion.ValueDeclaration, diagnostics.X_0_is_declared_here, suggestionName))
}
c.diagnostics.Add(diagnostic)
c.addErrorOrSuggestion(!isUncheckedJS, diagnostic)
return
}
// And then fall back to unspecified "not found"
Expand Down Expand Up @@ -10873,11 +10875,10 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
if c.checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol) {
return c.errorType
}
// !!!
// containingClass := getContainingClassExcludingClassDecorators(right)
// if containingClass && isPlainJSFile(ast.GetSourceFileOfNode(containingClass), c.compilerOptions.checkJs) {
// c.grammarErrorOnNode(right, diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, right.Text())
// }
containingClass := getContainingClassExcludingClassDecorators(right)
if containingClass != nil && ast.IsPlainJSFile(ast.GetSourceFileOfNode(containingClass), c.compilerOptions.CheckJs) {
c.grammarErrorOnNode(right, diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, right.Text())
}
} else {
isSetonlyAccessor := prop.Flags&ast.SymbolFlagsSetAccessor != 0 && prop.Flags&ast.SymbolFlagsGetAccessor == 0
if isSetonlyAccessor && assignmentKind != AssignmentKindDefinite {
Expand All @@ -10904,6 +10905,10 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
indexInfo = c.getApplicableIndexInfoForName(apparentType, right.Text())
}
if indexInfo == nil {
isUncheckedJS := c.isUncheckedJSSuggestion(node, leftType.symbol, true /*excludeClasses*/)
if !isUncheckedJS && c.isJSLiteralType(leftType) {
return c.anyType
}
if leftType.symbol == c.globalThisSymbol {
globalSymbol := c.globalThisSymbol.Exports[right.Text()]
if globalSymbol != nil && globalSymbol.Flags&ast.SymbolFlagsBlockScoped != 0 {
Expand All @@ -10914,7 +10919,7 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
return c.anyType
}
if right.Text() != "" && !c.checkAndReportErrorForExtendingInterface(node) {
c.reportNonexistentProperty(right, core.IfElse(isThisTypeParameter(leftType), apparentType, leftType))
c.reportNonexistentProperty(right, core.IfElse(isThisTypeParameter(leftType), apparentType, leftType), isUncheckedJS)
}
return c.errorType
}
Expand Down Expand Up @@ -11091,7 +11096,7 @@ func (c *Checker) checkPrivateIdentifierPropertyAccess(leftType *Type, right *as
return false
}

func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *Type) {
func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *Type, isUncheckedJS bool) {
if ast.IsJSDocNameReferenceContext(propNode) {
return
}
Expand Down Expand Up @@ -11123,7 +11128,8 @@ func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *
suggestion := c.getSuggestedSymbolForNonexistentProperty(propNode, containingType)
if suggestion != nil {
suggestedName := ast.SymbolName(suggestion)
diagnostic = NewDiagnosticChainForNode(diagnostic, propNode, diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, missingProperty, container, suggestedName)
message := core.IfElse(isUncheckedJS, diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2, diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2)
diagnostic = NewDiagnosticChainForNode(diagnostic, propNode, message, missingProperty, container, suggestedName)
if suggestion.ValueDeclaration != nil {
diagnostic.AddRelatedInfo(NewDiagnosticForNode(suggestion.ValueDeclaration, diagnostics.X_0_is_declared_here, suggestedName))
}
Expand All @@ -11140,7 +11146,7 @@ func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *
}
}
}
c.diagnostics.Add(diagnostic)
c.addErrorOrSuggestion(!isUncheckedJS || diagnostic.Code() != diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.Code(), diagnostic)
}

func (c *Checker) getSuggestedLibForNonExistentProperty(missingProperty string, containingType *Type) string {
Expand Down Expand Up @@ -12609,7 +12615,8 @@ func (c *Checker) checkInExpression(left *ast.Expression, right *ast.Expression,
// Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type
// which provides us with the opportunity to emit more detailed errors
if c.symbolNodeLinks.Get(left).resolvedSymbol == nil && ast.GetContainingClass(left) != nil {
c.reportNonexistentProperty(left, rightType)
isUncheckedJS := c.isUncheckedJSSuggestion(left, rightType.symbol, true /*excludeClasses*/)
c.reportNonexistentProperty(left, rightType, isUncheckedJS)
}
} else {
// The type of the left operand must be assignable to string, number, or symbol.
Expand Down Expand Up @@ -12714,6 +12721,9 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
}
result := c.newAnonymousType(node.Symbol(), propertiesTable, nil, nil, indexInfos)
result.objectFlags |= objectFlags | ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral
if contextualType == nil && ast.IsInJSFile(node) && !ast.IsInJsonFile(node) {
result.objectFlags |= ObjectFlagsJSLiteral
}
if patternWithComputedProperties {
result.objectFlags |= ObjectFlagsObjectLiteralPatternWithComputedProperties
}
Expand Down Expand Up @@ -17598,6 +17608,10 @@ func (c *Checker) widenTypeForVariableLikeDeclaration(t *Type, declaration *ast.
}

func (c *Checker) reportImplicitAny(declaration *ast.Node, t *Type, wideningKind WideningKind) {
if ast.IsInJSFile(declaration) && !ast.IsCheckJSEnabledForFile(ast.GetSourceFileOfNode(declaration), c.compilerOptions) {
// Only report implicit any errors/suggestions in TS and ts-check JS files
return
}
typeAsString := c.TypeToString(c.getWidenedType(t))
var diagnostic *diagnostics.Message
switch declaration.Kind {
Expand Down Expand Up @@ -26321,6 +26335,9 @@ func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectTy
if indexType.flags&TypeFlagsNever != 0 {
return c.neverType
}
if c.isJSLiteralType(objectType) {
return c.anyType
}
if accessExpression != nil && !isConstEnumObjectType(objectType) {
if isObjectLiteralType(objectType) {
if c.noImplicitAny && indexType.flags&(TypeFlagsStringLiteral|TypeFlagsNumberLiteral) != 0 {
Expand Down Expand Up @@ -26378,6 +26395,9 @@ func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectTy
if accessFlags&AccessFlagsAllowMissing != 0 && isObjectLiteralType(objectType) {
return c.undefinedType
}
if c.isJSLiteralType(objectType) {
return c.anyType
}
if accessNode != nil {
indexNode := getIndexNodeForAccessExpression(accessNode)
if indexNode.Kind != ast.KindBigIntLiteral && indexType.flags&(TypeFlagsStringLiteral|TypeFlagsNumberLiteral) != 0 {
Expand Down
89 changes: 89 additions & 0 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1825,3 +1825,92 @@ func nodeStartsNewLexicalEnvironment(node *ast.Node) bool {
}
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 ||
classOrConstructorParameterIsDecorated(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
}

func classOrConstructorParameterIsDecorated(node *ast.Node) bool {
if nodeIsDecorated(node, nil, nil) {
return true
}
constructor := ast.GetFirstConstructorWithBody(node)
return constructor != nil && childIsDecorated(constructor, node)
}

func nodeIsDecorated(node *ast.Node, parent *ast.Node, grandparent *ast.Node) bool {
return ast.HasDecorators(node) && nodeCanBeDecorated(false, node, parent, grandparent)
}
Comment on lines +1863 to +1867
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

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

The logic for suggestionHasNoExtendsOrDecorators is incorrect. This variable should be true when the suggestion is not a class OR when it is a class with neither extends nor decorators. However, lines 1866-1867 incorrectly use OR and check for the presence (not absence) of extends/decorators.

The current logic returns true when the class HAS extends OR HAS decorators, which is the opposite of what the variable name indicates.

The correct logic should be:

suggestionHasNoExtendsOrDecorators := suggestion == nil ||
    suggestion.ValueDeclaration == nil ||
    !ast.IsClassLike(suggestion.ValueDeclaration) ||
    (len(ast.GetExtendsHeritageClauseElements(suggestion.ValueDeclaration)) == 0 &&
     !classOrConstructorParameterIsDecorated(suggestion.ValueDeclaration))

Note: The last condition uses AND (not OR) and checks for the absence (== 0 and !) of extends/decorators.

Copilot uses AI. Check for mistakes.

func nodeOrChildIsDecorated(node *ast.Node, parent *ast.Node, grandparent *ast.Node) bool {
return nodeIsDecorated(node, parent, grandparent) || childIsDecorated(node, parent)
}

func childIsDecorated(node *ast.Node, parent *ast.Node) bool {
switch node.Kind {
case ast.KindClassDeclaration, ast.KindClassExpression:
return core.Some(node.Members(), func(m *ast.Node) bool {
return nodeOrChildIsDecorated(m, node, parent)
})
case ast.KindMethodDeclaration,
ast.KindSetAccessor,
ast.KindConstructor:
return core.Some(node.Parameters(), func(p *ast.Node) bool {
return nodeIsDecorated(p, node, parent)
})
default:
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
}
2 changes: 1 addition & 1 deletion internal/transformers/declarations/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,7 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl

modifiers := tx.ensureModifiers(input.AsNode())
typeParameters := tx.ensureTypeParams(input.AsNode(), input.TypeParameters)
ctor := getFirstConstructorWithBody(input.AsNode())
ctor := ast.GetFirstConstructorWithBody(input.AsNode())
var parameterProperties []*ast.Node
if ctor != nil {
oldDiag := tx.state.getSymbolAccessibilityDiagnostic
Expand Down
9 changes: 0 additions & 9 deletions internal/transformers/declarations/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,6 @@ func shouldEmitFunctionProperties(input *ast.FunctionDeclaration) bool {
return len(overloadSignatures) == 0 || overloadSignatures[len(overloadSignatures)-1] == input.AsNode()
}

func getFirstConstructorWithBody(node *ast.Node) *ast.Node {
for _, member := range node.Members() {
if ast.IsConstructorDeclaration(member) && ast.NodeIsPresent(member.Body()) {
return member
}
}
return nil
}

func getEffectiveBaseTypeNode(node *ast.Node) *ast.Node {
baseType := ast.GetClassExtendsHeritageElement(node)
// !!! TODO: JSDoc support
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
ExtendedClass.js(17,5): error TS1231: An export assignment must be at the top level of a file or module declaration.
ExtendedClass.js(17,12): error TS2339: Property 'exports' does not exist on type '{}'.
ExtendedClass.js(18,19): error TS2339: Property 'exports' does not exist on type '{}'.


==== typing.d.ts (0 errors) ====
Expand All @@ -12,7 +10,7 @@ ExtendedClass.js(18,19): error TS2339: Property 'exports' does not exist on type
}
export = BaseClass;
}
==== ExtendedClass.js (3 errors) ====
==== ExtendedClass.js (1 errors) ====
define("lib/ExtendedClass", ["deps/BaseClass"],
/**
* {typeof import("deps/BaseClass")}
Expand All @@ -32,9 +30,5 @@ ExtendedClass.js(18,19): error TS2339: Property 'exports' does not exist on type
module.exports = ExtendedClass
~~~~~~
!!! error TS1231: An export assignment must be at the top level of a file or module declaration.
~~~~~~~
!!! error TS2339: Property 'exports' does not exist on type '{}'.
return module.exports;
~~~~~~~
!!! error TS2339: Property 'exports' does not exist on type '{}'.
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
@@= skipped -0, +0 lines =@@
-<no content>
+ExtendedClass.js(17,5): error TS1231: An export assignment must be at the top level of a file or module declaration.
+ExtendedClass.js(17,12): error TS2339: Property 'exports' does not exist on type '{}'.
+ExtendedClass.js(18,19): error TS2339: Property 'exports' does not exist on type '{}'.
+
+
+==== typing.d.ts (0 errors) ====
Expand All @@ -16,7 +14,7 @@
+ }
+ export = BaseClass;
+ }
+==== ExtendedClass.js (3 errors) ====
+==== ExtendedClass.js (1 errors) ====
+ define("lib/ExtendedClass", ["deps/BaseClass"],
+ /**
+ * {typeof import("deps/BaseClass")}
Expand All @@ -36,9 +34,5 @@
+ module.exports = ExtendedClass
+ ~~~~~~
+!!! error TS1231: An export assignment must be at the top level of a file or module declaration.
+ ~~~~~~~
+!!! error TS2339: Property 'exports' does not exist on type '{}'.
+ return module.exports;
+ ~~~~~~~
+!!! error TS2339: Property 'exports' does not exist on type '{}'.
+ });

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
0.js(6,21): error TS1003: Identifier expected.
0.js(8,9): error TS2339: Property 'SomeName' does not exist on type '{}'.
0.js(10,12): error TS2503: Cannot find namespace 'exports'.


==== 0.js (3 errors) ====
==== 0.js (2 errors) ====
// @ts-check

var exports = {};
Expand All @@ -15,8 +14,6 @@

!!! error TS1003: Identifier expected.
exports.SomeName;
~~~~~~~~
!!! error TS2339: Property 'SomeName' does not exist on type '{}'.

/** @type {exports.SomeName} */
~~~~~~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
-
-==== 0.js (1 errors) ====
+0.js(6,21): error TS1003: Identifier expected.
+0.js(8,9): error TS2339: Property 'SomeName' does not exist on type '{}'.
+0.js(10,12): error TS2503: Cannot find namespace 'exports'.
+
+
+==== 0.js (3 errors) ====
+==== 0.js (2 errors) ====
// @ts-check

var exports = {};
Expand All @@ -22,8 +21,6 @@
+
+!!! error TS1003: Identifier expected.
exports.SomeName;
+ ~~~~~~~~
+!!! error TS2339: Property 'SomeName' does not exist on type '{}'.

/** @type {exports.SomeName} */
- ~~~~~~~~
Expand Down
Loading