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
46 changes: 32 additions & 14 deletions internal/ast/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import (

"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/locale"
)

// Diagnostic

type Diagnostic struct {
file *SourceFile
loc core.TextRange
code int32
category diagnostics.Category
message string
file *SourceFile
loc core.TextRange
code int32
category diagnostics.Category
// Original message; may be nil.
message *diagnostics.Message
messageKey diagnostics.Key
messageArgs []string
messageChain []*Diagnostic
relatedInformation []*Diagnostic
reportsUnnecessary bool
Expand All @@ -31,7 +35,8 @@ func (d *Diagnostic) Len() int { return d.loc.Len() }
func (d *Diagnostic) Loc() core.TextRange { return d.loc }
func (d *Diagnostic) Code() int32 { return d.code }
func (d *Diagnostic) Category() diagnostics.Category { return d.category }
func (d *Diagnostic) Message() string { return d.message }
func (d *Diagnostic) MessageKey() diagnostics.Key { return d.messageKey }
func (d *Diagnostic) MessageArgs() []string { return d.messageArgs }
func (d *Diagnostic) MessageChain() []*Diagnostic { return d.messageChain }
func (d *Diagnostic) RelatedInformation() []*Diagnostic { return d.relatedInformation }
func (d *Diagnostic) ReportsUnnecessary() bool { return d.reportsUnnecessary }
Expand Down Expand Up @@ -72,12 +77,22 @@ func (d *Diagnostic) Clone() *Diagnostic {
return &result
}

func NewDiagnosticWith(
func (d *Diagnostic) Localize(locale locale.Locale) string {
return diagnostics.Localize(locale, d.message, d.messageKey, d.messageArgs...)
}

// For debugging only.
func (d *Diagnostic) String() string {
return diagnostics.Localize(locale.Default, d.message, d.messageKey, d.messageArgs...)
}

func NewDiagnosticFromSerialized(
file *SourceFile,
loc core.TextRange,
code int32,
category diagnostics.Category,
message string,
messageKey diagnostics.Key,
messageArgs []string,
messageChain []*Diagnostic,
relatedInformation []*Diagnostic,
reportsUnnecessary bool,
Expand All @@ -89,7 +104,8 @@ func NewDiagnosticWith(
loc: loc,
code: code,
category: category,
message: message,
messageKey: messageKey,
messageArgs: messageArgs,
messageChain: messageChain,
relatedInformation: relatedInformation,
reportsUnnecessary: reportsUnnecessary,
Expand All @@ -104,7 +120,9 @@ func NewDiagnostic(file *SourceFile, loc core.TextRange, message *diagnostics.Me
loc: loc,
code: message.Code(),
category: message.Category(),
message: message.Format(args...),
message: message,
messageKey: message.Key(),
messageArgs: diagnostics.StringifyArgs(args),
reportsUnnecessary: message.ReportsUnnecessary(),
reportsDeprecated: message.ReportsDeprecated(),
}
Expand Down Expand Up @@ -185,13 +203,13 @@ func EqualDiagnosticsNoRelatedInfo(d1, d2 *Diagnostic) bool {
return getDiagnosticPath(d1) == getDiagnosticPath(d2) &&
d1.Loc() == d2.Loc() &&
d1.Code() == d2.Code() &&
d1.Message() == d2.Message() &&
slices.Equal(d1.MessageArgs(), d2.MessageArgs()) &&
slices.EqualFunc(d1.MessageChain(), d2.MessageChain(), equalMessageChain)
}

func equalMessageChain(c1, c2 *Diagnostic) bool {
return c1.Code() == c2.Code() &&
c1.Message() == c2.Message() &&
slices.Equal(c1.MessageArgs(), c2.MessageArgs()) &&
slices.EqualFunc(c1.MessageChain(), c2.MessageChain(), equalMessageChain)
}

Expand All @@ -211,7 +229,7 @@ func compareMessageChainSize(c1, c2 []*Diagnostic) int {

func compareMessageChainContent(c1, c2 []*Diagnostic) int {
for i := range c1 {
c := strings.Compare(c1[i].Message(), c2[i].Message())
c := slices.Compare(c1[i].MessageArgs(), c2[i].MessageArgs())
if c != 0 {
return c
}
Expand Down Expand Up @@ -256,7 +274,7 @@ func CompareDiagnostics(d1, d2 *Diagnostic) int {
if c != 0 {
return c
}
c = strings.Compare(d1.Message(), d2.Message())
c = slices.Compare(d1.MessageArgs(), d2.MessageArgs())
if c != 0 {
return c
}
Expand Down
3 changes: 2 additions & 1 deletion internal/bundled/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/repo"
"github.com/microsoft/typescript-go/internal/tspath"
Expand Down Expand Up @@ -150,7 +151,7 @@ func readLibs() []lib {

if len(diags) > 0 {
for _, diag := range diags {
log.Printf("%s", diag.Message())
log.Printf("%s", diag.Localize(locale.Default))
}
log.Fatalf("failed to parse libs.json")
}
Expand Down
44 changes: 28 additions & 16 deletions internal/checker/jsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
if !ast.IsJsxSpreadAttribute(prop) && !isHyphenatedJsxName(prop.Name().Text()) {
nameType := c.getStringLiteralType(prop.Name().Text())
if nameType != nil && nameType.flags&TypeFlagsNever == 0 {
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, nil, diagnosticOutput) || reportedError
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, nil, nil, diagnosticOutput) || reportedError
}
}
}
Expand Down Expand Up @@ -326,13 +326,14 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
nonArrayLikeTargetParts = c.filterType(childrenTargetType, func(t *Type) bool { return !c.isArrayOrTupleLikeType(t) })
}
var invalidTextDiagnostic *diagnostics.Message
getInvalidTextualChildDiagnostic := func() *diagnostics.Message {
var invalidTextDiagnosticArgs []any
getInvalidTextualChildDiagnostic := func() (*diagnostics.Message, []any) {
if invalidTextDiagnostic == nil {
tagNameText := scanner.GetTextOfNode(node.Parent.TagName())
diagnostic := diagnostics.X_0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2
invalidTextDiagnostic = diagnostics.FormatMessage(diagnostic, tagNameText, childrenPropName, c.TypeToString(childrenTargetType))
invalidTextDiagnostic = diagnostics.X_0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2
invalidTextDiagnosticArgs = []any{tagNameText, childrenPropName, c.TypeToString(childrenTargetType)}
}
return invalidTextDiagnostic
return invalidTextDiagnostic, invalidTextDiagnosticArgs
}
if moreThanOneRealChildren {
if arrayLikeTargetParts != c.neverType {
Expand All @@ -350,7 +351,7 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
child := validChildren[0]
e := c.getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic)
if e.errorNode != nil {
reportedError = c.elaborateElement(source, target, relation, e.errorNode, e.innerExpression, e.nameType, e.errorMessage, diagnosticOutput) || reportedError
reportedError = c.elaborateElement(source, target, relation, e.errorNode, e.innerExpression, e.nameType, nil, e.createDiagnostic, diagnosticOutput) || reportedError
}
} else if !c.isTypeRelatedTo(c.getIndexedAccessType(source, childrenNameType), childrenTargetType, relation) {
// arity mismatch
Expand All @@ -364,13 +365,13 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
}

type JsxElaborationElement struct {
errorNode *ast.Node
innerExpression *ast.Node
nameType *Type
errorMessage *diagnostics.Message
errorNode *ast.Node
innerExpression *ast.Node
nameType *Type
createDiagnostic func(prop *ast.Node) *ast.Diagnostic // Optional: creates a custom diagnostic for this element
}

func (c *Checker) generateJsxChildren(node *ast.Node, getInvalidTextDiagnostic func() *diagnostics.Message) iter.Seq[JsxElaborationElement] {
func (c *Checker) generateJsxChildren(node *ast.Node, getInvalidTextDiagnostic func() (*diagnostics.Message, []any)) iter.Seq[JsxElaborationElement] {
return func(yield func(JsxElaborationElement) bool) {
memberOffset := 0
for i, child := range node.Children().Nodes {
Expand All @@ -387,7 +388,7 @@ func (c *Checker) generateJsxChildren(node *ast.Node, getInvalidTextDiagnostic f
}
}

func (c *Checker) getElaborationElementForJsxChild(child *ast.Node, nameType *Type, getInvalidTextDiagnostic func() *diagnostics.Message) JsxElaborationElement {
func (c *Checker) getElaborationElementForJsxChild(child *ast.Node, nameType *Type, getInvalidTextDiagnostic func() (*diagnostics.Message, []any)) JsxElaborationElement {
switch child.Kind {
case ast.KindJsxExpression:
// child is of the type of the expression
Expand All @@ -398,7 +399,15 @@ func (c *Checker) getElaborationElementForJsxChild(child *ast.Node, nameType *Ty
return JsxElaborationElement{}
}
// child is a string
return JsxElaborationElement{errorNode: child, innerExpression: nil, nameType: nameType, errorMessage: getInvalidTextDiagnostic()}
return JsxElaborationElement{
errorNode: child,
innerExpression: nil,
nameType: nameType,
createDiagnostic: func(prop *ast.Node) *ast.Diagnostic {
errorMessage, errorArgs := getInvalidTextDiagnostic()
return NewDiagnosticForNode(prop, errorMessage, errorArgs...)
},
}
case ast.KindJsxElement, ast.KindJsxSelfClosingElement, ast.KindJsxFragment:
// child is of type JSX.Element
return JsxElaborationElement{errorNode: child, innerExpression: child, nameType: nameType}
Expand Down Expand Up @@ -448,18 +457,21 @@ func (c *Checker) elaborateIterableOrArrayLikeTargetElementwise(iterator iter.Se
if next != nil {
specificSource = c.checkExpressionForMutableLocationWithContextualType(next, sourcePropType)
}
if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
if e.createDiagnostic != nil {
// Use the custom diagnostic factory if provided (e.g., for JSX text children with dynamic error messages)
c.reportDiagnostic(e.createDiagnostic(prop), diagnosticOutput)
} else if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
diag := createDiagnosticForNode(prop, diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, c.TypeToString(specificSource), c.TypeToString(targetPropType))
c.reportDiagnostic(diag, diagnosticOutput)
} else {
targetIsOptional := propName != ast.InternalSymbolNameMissing && core.OrElse(c.getPropertyOfType(tupleOrArrayLikeTargetParts, propName), c.unknownSymbol).Flags&ast.SymbolFlagsOptional != 0
sourceIsOptional := propName != ast.InternalSymbolNameMissing && core.OrElse(c.getPropertyOfType(source, propName), c.unknownSymbol).Flags&ast.SymbolFlagsOptional != 0
targetPropType = c.removeMissingType(targetPropType, targetIsOptional)
sourcePropType = c.removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional)
result := c.checkTypeRelatedToEx(specificSource, targetPropType, relation, prop, e.errorMessage, diagnosticOutput)
result := c.checkTypeRelatedToEx(specificSource, targetPropType, relation, prop, nil, diagnosticOutput)
if result && specificSource != sourcePropType {
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
c.checkTypeRelatedToEx(sourcePropType, targetPropType, relation, prop, e.errorMessage, diagnosticOutput)
c.checkTypeRelatedToEx(sourcePropType, targetPropType, relation, prop, nil, diagnosticOutput)
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,10 @@ func (c *Checker) elaborateObjectLiteral(node *ast.Node, source *Type, target *T
}
switch prop.Kind {
case ast.KindSetAccessor, ast.KindGetAccessor, ast.KindMethodDeclaration, ast.KindShorthandPropertyAssignment:
reportedError = c.elaborateElement(source, target, relation, prop.Name(), nil, nameType, nil, diagnosticOutput) || reportedError
reportedError = c.elaborateElement(source, target, relation, prop.Name(), nil, nameType, nil, nil, diagnosticOutput) || reportedError
case ast.KindPropertyAssignment:
message := core.IfElse(ast.IsComputedNonLiteralName(prop.Name()), diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1, nil)
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, message, diagnosticOutput) || reportedError
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, message, nil, diagnosticOutput) || reportedError
}
}
return reportedError
Expand All @@ -531,12 +531,12 @@ func (c *Checker) elaborateArrayLiteral(node *ast.Node, source *Type, target *Ty
}
nameType := c.getNumberLiteralType(jsnum.Number(i))
checkNode := c.getEffectiveCheckNode(element)
reportedError = c.elaborateElement(source, target, relation, checkNode, checkNode, nameType, nil, diagnosticOutput) || reportedError
reportedError = c.elaborateElement(source, target, relation, checkNode, checkNode, nameType, nil, nil, diagnosticOutput) || reportedError
}
return reportedError
}

func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relation, prop *ast.Node, next *ast.Node, nameType *Type, errorMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relation, prop *ast.Node, next *ast.Node, nameType *Type, errorMessage *diagnostics.Message, diagnosticFactory func(prop *ast.Node) *ast.Diagnostic, diagnosticOutput *[]*ast.Diagnostic) bool {
targetPropType := c.getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType)
if targetPropType == nil || targetPropType.flags&TypeFlagsIndexedAccess != 0 {
// Don't elaborate on indexes on generic variables
Expand All @@ -557,7 +557,10 @@ func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relatio
if next != nil {
specificSource = c.checkExpressionForMutableLocationWithContextualType(next, sourcePropType)
}
if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
if diagnosticFactory != nil {
// Use the custom diagnostic factory if provided (e.g., for JSX text children with dynamic error messages)
diags = append(diags, diagnosticFactory(prop))
} else if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
diags = append(diags, createDiagnosticForNode(prop, diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, c.TypeToString(specificSource), c.TypeToString(targetPropType)))
} else {
propName := c.getPropertyNameFromIndex(nameType, nil /*accessNode*/)
Expand Down
10 changes: 5 additions & 5 deletions internal/compiler/fileloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
type libResolution struct {
libraryName string
resolution *module.ResolvedModule
trace []string
trace []module.DiagAndArgs
}

type LibFile struct {
Expand Down Expand Up @@ -231,7 +231,7 @@ func processAllProgramFiles(
module.ModeAwareCacheKey{Name: value.libraryName, Mode: core.ModuleKindCommonJS}: value.resolution,
}
for _, trace := range value.trace {
opts.Host.Trace(trace)
opts.Host.Trace(trace.Message, trace.Args...)
}
}

Expand Down Expand Up @@ -286,7 +286,7 @@ func (p *fileLoader) addAutomaticTypeDirectiveTasks() {
func (p *fileLoader) resolveAutomaticTypeDirectives(containingFileName string) (
toParse []resolvedRef,
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective],
typeResolutionsTrace []string,
typeResolutionsTrace []module.DiagAndArgs,
) {
automaticTypeDirectiveNames := module.GetAutomaticTypeDirectiveNames(p.opts.Config.CompilerOptions(), p.opts.Host)
if len(automaticTypeDirectiveNames) != 0 {
Expand Down Expand Up @@ -449,7 +449,7 @@ func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) {
meta := t.metadata

typeResolutionsInFile := make(module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], len(file.TypeReferenceDirectives))
var typeResolutionsTrace []string
var typeResolutionsTrace []module.DiagAndArgs
for index, ref := range file.TypeReferenceDirectives {
redirect, fileName := p.projectReferenceFileMapper.getRedirectForResolution(file)
resolutionMode := getModeForTypeReferenceDirectiveInFile(ref, file, meta, module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect))
Expand Down Expand Up @@ -527,7 +527,7 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) {

if len(moduleNames) != 0 {
resolutionsInFile := make(module.ModeAwareCache[*module.ResolvedModule], len(moduleNames))
var resolutionsTrace []string
var resolutionsTrace []module.DiagAndArgs

for index, entry := range moduleNames {
moduleName := entry.Text()
Expand Down
8 changes: 4 additions & 4 deletions internal/compiler/filesparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ type parseTask struct {

metadata ast.SourceFileMetaData
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule]
resolutionsTrace []string
resolutionsTrace []module.DiagAndArgs
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective]
typeResolutionsTrace []string
typeResolutionsTrace []module.DiagAndArgs
resolutionDiagnostics []*ast.Diagnostic
importHelpersImportSpecifier *ast.Node
jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier
Expand Down Expand Up @@ -255,10 +255,10 @@ func (w *filesParser) collectWorker(loader *fileLoader, tasks []*parseTask, iter
continue
}
for _, trace := range task.typeResolutionsTrace {
loader.opts.Host.Trace(trace)
loader.opts.Host.Trace(trace.Message, trace.Args...)
}
for _, trace := range task.resolutionsTrace {
loader.opts.Host.Trace(trace)
loader.opts.Host.Trace(trace.Message, trace.Args...)
}
if subTasks := task.subTasks; len(subTasks) > 0 {
w.collectWorker(loader, subTasks, iterate, seen)
Expand Down
Loading
Loading