Skip to content
Draft
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
12 changes: 6 additions & 6 deletions internal/binder/referenceresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type ReferenceResolver interface {

type ReferenceResolverHooks struct {
ResolveName func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol
GetResolvedSymbol func(*ast.Node) *ast.Symbol
GetCachedResolvedSymbol func(*ast.Node) *ast.Symbol
GetMergedSymbol func(*ast.Symbol) *ast.Symbol
GetParentOfSymbol func(*ast.Symbol) *ast.Symbol
GetSymbolOfDeclaration func(*ast.Declaration) *ast.Symbol
Expand All @@ -41,10 +41,10 @@ func NewReferenceResolver(options *core.CompilerOptions, hooks ReferenceResolver
}
}

func (r *referenceResolver) getResolvedSymbol(node *ast.Node) *ast.Symbol {
func (r *referenceResolver) getCachedResolvedSymbol(node *ast.Node) *ast.Symbol {
if node != nil {
if r.hooks.GetResolvedSymbol != nil {
return r.hooks.GetResolvedSymbol(node)
if r.hooks.GetCachedResolvedSymbol != nil {
return r.hooks.GetCachedResolvedSymbol(node)
}
}
return nil
Expand Down Expand Up @@ -81,7 +81,7 @@ func (r *referenceResolver) getSymbolOfDeclaration(declaration *ast.Declaration)
}

func (r *referenceResolver) getReferencedValueSymbol(reference *ast.IdentifierNode, startInDeclarationContainer bool) *ast.Symbol {
resolvedSymbol := r.getResolvedSymbol(reference)
resolvedSymbol := r.getCachedResolvedSymbol(reference)
if resolvedSymbol != nil {
return resolvedSymbol
}
Expand Down Expand Up @@ -250,7 +250,7 @@ func (r *referenceResolver) GetElementAccessExpressionName(expression *ast.Eleme

func (r *referenceResolver) GetReferencedMemberValueDeclaration(node *ast.Node) *ast.Declaration {
// member references are `this.something` or `this[something]`, so should always simply have a resolved symbol
s := r.getResolvedSymbol(node)
s := r.getCachedResolvedSymbol(node)
if s == nil && node.Symbol() != nil {
// might be a declaration instead of a ref, get the merged declaration symbol
s = r.getMergedSymbol(node.Symbol())
Expand Down
34 changes: 21 additions & 13 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2982,7 +2982,7 @@ func (c *Checker) checkTypeReferenceOrImport(node *ast.Node) {
c.checkTypeArgumentConstraints(node, typeParameters)
}
}
symbol := c.getResolvedSymbolOrNil(node)
symbol := c.getCachedResolvedSymbol(node)
if symbol != nil {
if core.Some(symbol.Declarations, func(d *ast.Node) bool { return ast.IsTypeDeclaration(d) && c.IsDeprecatedDeclaration(d) }) {
c.addDeprecatedSuggestion(c.getDeprecatedSuggestionNode(node), symbol.Declarations, symbol.Name)
Expand Down Expand Up @@ -3815,7 +3815,7 @@ func (c *Checker) checkTestingKnownTruthyType(condExpr *ast.Node, condType *Type
if location != condExpr {
t = c.checkExpression(location)
}
if t.flags&TypeFlagsEnumLiteral != 0 && ast.IsPropertyAccessExpression(location) && core.OrElse(c.getResolvedSymbolOrNil(location.Expression()), c.unknownSymbol).Flags&ast.SymbolFlagsEnum != 0 {
if t.flags&TypeFlagsEnumLiteral != 0 && ast.IsPropertyAccessExpression(location) && core.OrElse(c.getCachedResolvedSymbol(location.Expression()), c.unknownSymbol).Flags&ast.SymbolFlagsEnum != 0 {
// EnumLiteral type at condition with known value is always truthy or always falsy, likely an error
c.error(location, diagnostics.This_condition_will_always_return_0, core.IfElse(evaluator.IsTruthy(t.AsLiteralType().value), "true", "false"))
return
Expand Down Expand Up @@ -8135,7 +8135,7 @@ func (c *Checker) checkElementAccessExpression(node *ast.Node, exprType *Type, c
core.IfElse(c.isGenericObjectType(objectType) && !isThisTypeParameter(objectType), AccessFlagsNoIndexSignatures, 0)
}
indexedAccessType := core.OrElse(c.getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node, nil), c.errorType)
return c.checkIndexedAccessIndexType(c.getFlowTypeOfAccessExpression(node, c.getResolvedSymbolOrNil(node), indexedAccessType, indexExpression, checkMode), node)
return c.checkIndexedAccessIndexType(c.getFlowTypeOfAccessExpression(node, c.getCachedResolvedSymbol(node), indexedAccessType, indexExpression, checkMode), node)
}

// Return true if given node is an expression consisting of an identifier (possibly parenthesized)
Expand Down Expand Up @@ -9937,7 +9937,7 @@ func (c *Checker) invocationErrorDetails(errorTarget *ast.Node, apparentType *Ty
headMessage := core.IfElse(isCall, diagnostics.This_expression_is_not_callable, diagnostics.This_expression_is_not_constructable)
// Diagnose get accessors incorrectly called as functions
if ast.IsCallExpression(errorTarget.Parent) && len(errorTarget.Parent.Arguments()) == 0 {
resolvedSymbol := c.getResolvedSymbolOrNil(errorTarget)
resolvedSymbol := c.getCachedResolvedSymbol(errorTarget)
if resolvedSymbol != nil && resolvedSymbol.Flags&ast.SymbolFlagsGetAccessor != 0 {
headMessage = diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without
}
Expand Down Expand Up @@ -10766,7 +10766,7 @@ func (c *Checker) checkDeleteExpression(node *ast.Node) *Type {
if ast.IsPropertyAccessExpression(expr) && ast.IsPrivateIdentifier(expr.Name()) {
c.error(expr, diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier)
}
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbolOrNil(expr))
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getCachedResolvedSymbol(expr))
if symbol != nil {
if c.isReadonlySymbol(symbol) {
c.error(expr, diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property)
Expand Down Expand Up @@ -11209,7 +11209,7 @@ func (c *Checker) checkPropertyAccessChain(node *ast.Node, checkMode CheckMode)
}

func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, left *ast.Node, leftType *Type, right *ast.Node, checkMode CheckMode, writeOnly bool) *Type {
parentSymbol := c.getResolvedSymbolOrNil(left)
parentSymbol := c.getCachedResolvedSymbol(left)
assignmentKind := getAssignmentTargetKind(node)
widenedType := leftType
if assignmentKind != AssignmentKindNone || c.isMethodAccessForCall(node) {
Expand Down Expand Up @@ -13789,7 +13789,7 @@ func (c *Checker) checkExpressionForMutableLocation(node *ast.Node, checkMode Ch
}
}

func (c *Checker) getResolvedSymbol(node *ast.Node) *ast.Symbol {
func (c *Checker) getResolvedSymbol(node *ast.IdentifierNode) *ast.Symbol {
links := c.symbolNodeLinks.Get(node)
if links.resolvedSymbol == nil {
var symbol *ast.Symbol
Expand All @@ -13802,7 +13802,7 @@ func (c *Checker) getResolvedSymbol(node *ast.Node) *ast.Symbol {
return links.resolvedSymbol
}

func (c *Checker) getResolvedSymbolOrNil(node *ast.Node) *ast.Symbol {
func (c *Checker) getCachedResolvedSymbol(node *ast.Node) *ast.Symbol {
return c.symbolNodeLinks.Get(node).resolvedSymbol
}

Expand Down Expand Up @@ -14880,7 +14880,7 @@ func (c *Checker) getTargetOfAliasLikeExpression(expression *ast.Node) *ast.Symb
return aliasLike
}
c.checkExpressionCached(expression)
return c.getResolvedSymbolOrNil(expression)
return c.getCachedResolvedSymbol(expression)
}

func (c *Checker) getTargetOfNamespaceExportDeclaration(node *ast.Node) *ast.Symbol {
Expand Down Expand Up @@ -17041,7 +17041,7 @@ func (c *Checker) getInferredTypeParameterConstraint(t *Type, omitTypeReferences
func (c *Checker) getTypeParametersForTypeReferenceOrImport(node *ast.Node) []*Type {
t := c.getTypeFromTypeNode(node)
if !c.isErrorType(t) {
symbol := c.getResolvedSymbolOrNil(node)
symbol := c.getCachedResolvedSymbol(node)
if symbol != nil {
return c.getTypeParametersForTypeAndSymbol(t, symbol)
}
Expand Down Expand Up @@ -28045,8 +28045,16 @@ func isInternalModuleImportEqualsDeclaration(node *ast.Node) bool {
}

func (c *Checker) markIdentifierAliasReferenced(location *ast.IdentifierNode) {
symbol := c.getResolvedSymbol(location)
if symbol != nil && symbol != c.argumentsSymbol && symbol != c.unknownSymbol && !ast.IsThisInTypeQuery(location) {
if ast.IsThisInTypeQuery(location) {
return
}
symbol := c.getCachedResolvedSymbol(location)
if symbol == nil {
// lookup, no diagnostics
symbol = c.resolveName(location, location.AsIdentifier().Text, ast.SymbolFlagsValue|ast.SymbolFlagsExportValue,
nil, !ast.IsWriteOnlyAccess(location), false /*excludeGlobals*/)
}
if symbol != nil && symbol != c.argumentsSymbol && symbol != c.unknownSymbol {
c.markAliasReferenced(symbol, location)
}
}
Expand Down Expand Up @@ -31433,7 +31441,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast
possibleImportNode := isImportTypeQualifierPart(name)
if possibleImportNode != nil {
c.getTypeFromTypeNode(possibleImportNode)
sym := c.getResolvedSymbolOrNil(name)
sym := c.getCachedResolvedSymbol(name)
return core.IfElse(sym == c.unknownSymbol, nil, sym)
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ func (r *EmitResolver) getReferenceResolver() binder.ReferenceResolver {
if r.referenceResolver == nil {
r.referenceResolver = binder.NewReferenceResolver(r.checker.compilerOptions, binder.ReferenceResolverHooks{
ResolveName: r.checker.resolveName,
GetResolvedSymbol: r.checker.getResolvedSymbolOrNil,
GetCachedResolvedSymbol: r.checker.getCachedResolvedSymbol,
GetMergedSymbol: r.checker.getMergedSymbol,
GetParentOfSymbol: r.checker.getParentOfSymbol,
GetSymbolOfDeclaration: r.checker.getSymbolOfDeclaration,
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -1800,7 +1800,7 @@ func (c *Checker) isConstantReference(node *ast.Node) bool {
case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression:
// The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here.
if c.isConstantReference(node.Expression()) {
symbol := c.getResolvedSymbolOrNil(node)
symbol := c.getCachedResolvedSymbol(node)
if symbol != nil {
return c.isReadonlySymbol(symbol)
}
Expand Down
9 changes: 9 additions & 0 deletions internal/diagnostics/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,12 @@ func StringifyArgs(args []any) []string {
}
return result
}

func NewAdHocMessage(message string) *Message {
return &Message{
code: -1,
category: CategoryError,
key: "-1",
text: message,
}
}
1 change: 0 additions & 1 deletion internal/execute/incremental/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ func (p *Program) GetSuggestionDiagnostics(ctx context.Context, file *ast.Source
return p.program.GetSuggestionDiagnostics(ctx, file) // TODO: incremental suggestion diagnostics (only relevant in editor incremental builder?)
}

// GetModeForUsageLocation implements compiler.AnyProgram interface.
func (p *Program) Emit(ctx context.Context, options compiler.EmitOptions) *compiler.EmitResult {
p.panicIfNoProgram("Emit")

Expand Down
115 changes: 65 additions & 50 deletions internal/testutil/harnessutil/harnessutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,21 @@ func createCompilerHost(fs vfs.FS, defaultLibraryPath string, currentDirectory s
}
}

func getAllDiagnostics(ctx context.Context, program compiler.ProgramLike, captureSuggestions bool) []*ast.Diagnostic {
var diags []*ast.Diagnostic
diags = append(diags, program.GetProgramDiagnostics()...)
diags = append(diags, program.GetSyntacticDiagnostics(ctx, nil)...)
diags = append(diags, program.GetSemanticDiagnostics(ctx, nil)...)
diags = append(diags, program.GetGlobalDiagnostics(ctx)...)
if program.Options().GetEmitDeclarations() {
diags = append(diags, program.GetDeclarationDiagnostics(ctx, nil)...)
}
if captureSuggestions {
diags = append(diags, program.GetSuggestionDiagnostics(ctx, nil)...)
}
return compiler.SortAndDeduplicateDiagnostics(diags)
}

func compileFilesWithHost(
host compiler.CompilerHost,
config *tsoptions.ParsedCommandLine,
Expand All @@ -618,62 +633,62 @@ func compileFilesWithHost(
// delete compilerOptions.project;
// }

// !!! Need `getPreEmitDiagnostics` program for this
// pre-emit/post-emit error comparison requires declaration emit twice, which can be slow. If it's unlikely to flag any error consistency issues
// and if the test is running `skipLibCheck` - an indicator that we want the tets to run quickly - skip the before/after error comparison, too
// skipErrorComparison := len(rootFiles) >= 100 || options.SkipLibCheck == core.TSTrue && options.Declaration == core.TSTrue
// var preProgram *compiler.Program
// if !skipErrorComparison {
// preProgram = ts.createProgram({ rootNames: rootFiles || [], options: { ...compilerOptions, configFile: compilerOptions.configFile, traceResolution: false }, host, typeScriptVersion })
// }
// let preErrors = preProgram && ts.getPreEmitDiagnostics(preProgram);
// if (preProgram && harnessOptions.captureSuggestions) {
// preErrors = ts.concatenate(preErrors, ts.flatMap(preProgram.getSourceFiles(), f => preProgram.getSuggestionDiagnostics(f)));
// }
skipErrorComparison := len(config.FileNames()) >= 100 || config.CompilerOptions().SkipLibCheck == core.TSTrue && config.CompilerOptions().Declaration == core.TSTrue

// const program = ts.createProgram({ rootNames: rootFiles || [], options: compilerOptions, host, harnessOptions.typeScriptVersion });
// const emitResult = program.emit();
// let postErrors = ts.getPreEmitDiagnostics(program);
// !!! Need `getSuggestionDiagnostics` for this
// if (harnessOptions.captureSuggestions) {
// postErrors = ts.concatenate(postErrors, ts.flatMap(program.getSourceFiles(), f => program.getSuggestionDiagnostics(f)));
// }
// const longerErrors = ts.length(preErrors) > postErrors.length ? preErrors : postErrors;
// const shorterErrors = longerErrors === preErrors ? postErrors : preErrors;
// const errors = preErrors && (preErrors.length !== postErrors.length) ? [
// ...shorterErrors!,
// ts.addRelatedInfo(
// ts.createCompilerDiagnostic({
// category: ts.DiagnosticCategory.Error,
// code: -1,
// key: "-1",
// message: `Pre-emit (${preErrors.length}) and post-emit (${postErrors.length}) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here!`,
// }),
// ts.createCompilerDiagnostic({
// category: ts.DiagnosticCategory.Error,
// code: -1,
// key: "-1",
// message: `The excess diagnostics are:`,
// }),
// ...ts.filter(longerErrors!, p => !ts.some(shorterErrors, p2 => ts.compareDiagnostics(p, p2) === ts.Comparison.EqualTo)),
// ),
// ] : postErrors;
ctx := context.Background()
program := createProgram(host, config)
var diagnostics []*ast.Diagnostic
diagnostics = append(diagnostics, program.GetProgramDiagnostics()...)
diagnostics = append(diagnostics, program.GetSyntacticDiagnostics(ctx, nil)...)
diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, nil)...)
diagnostics = append(diagnostics, program.GetGlobalDiagnostics(ctx)...)
if config.CompilerOptions().GetEmitDeclarations() {
diagnostics = append(diagnostics, program.GetDeclarationDiagnostics(ctx, nil)...)
}
if harnessOptions.CaptureSuggestions {
diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, nil)...)
var preErrors []*ast.Diagnostic
if !skipErrorComparison {
preCompilerOptions := config.CompilerOptions().Clone()
preCompilerOptions.TraceResolution = core.TSFalse
preConfig := &tsoptions.ParsedCommandLine{
ParsedConfig: &core.ParsedOptions{
CompilerOptions: preCompilerOptions,
FileNames: config.FileNames(),
},
ConfigFile: config.ConfigFile,
Errors: config.Errors,
}
preProgram := createProgram(host, preConfig)
preErrors = getAllDiagnostics(ctx, preProgram, harnessOptions.CaptureSuggestions)
}

postProgram := createProgram(host, config)
// Force checking to happen before emit, otherwise emit may mutate checker state in a way that affects diagnostics.
_ = getAllDiagnostics(ctx, postProgram, harnessOptions.CaptureSuggestions)
emitResult := postProgram.Emit(ctx, compiler.EmitOptions{})
postErrors := getAllDiagnostics(ctx, postProgram, harnessOptions.CaptureSuggestions)

errors := postErrors
if !skipErrorComparison && len(postErrors) != len(preErrors) {
longerErrors := postErrors
shorterErrors := preErrors
if len(preErrors) > len(postErrors) {
longerErrors, shorterErrors = preErrors, postErrors
}
diag := ast.NewCompilerDiagnostic(
diagnostics.NewAdHocMessage(fmt.Sprintf("Pre-emit (%d) and post-emit (%d) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here!", len(preErrors), len(postErrors))),
)
diag = diag.AddRelatedInfo(ast.NewCompilerDiagnostic(diagnostics.NewAdHocMessage("The excess diagnostics are:")))
for _, d := range longerErrors {
matched := false
for _, d2 := range shorterErrors {
comparison := ast.CompareDiagnostics(d, d2)
if comparison == 0 {
matched = true
break
}
}
if !matched {
diag = diag.AddRelatedInfo(d)
}
}
errors = shorterErrors
errors = append(errors, diag)
}
emitResult := program.Emit(ctx, compiler.EmitOptions{})

return newCompilationResult(host, config.CompilerOptions(), program, emitResult, diagnostics, harnessOptions)
return newCompilationResult(host, config.CompilerOptions(), postProgram, emitResult, errors, harnessOptions)
}

type CompilationResult struct {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error TS-1: Pre-emit (92) and post-emit (93) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here!
constructorWithIncompleteTypeAnnotation.ts(11,13): error TS2503: Cannot find namespace 'module'.
constructorWithIncompleteTypeAnnotation.ts(11,13): error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
constructorWithIncompleteTypeAnnotation.ts(11,19): error TS1005: ';' expected.
constructorWithIncompleteTypeAnnotation.ts(22,35): error TS1005: ')' expected.
constructorWithIncompleteTypeAnnotation.ts(22,39): error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
Expand Down Expand Up @@ -93,7 +93,10 @@ constructorWithIncompleteTypeAnnotation.ts(259,55): error TS1005: ';' expected.
constructorWithIncompleteTypeAnnotation.ts(261,1): error TS1128: Declaration or statement expected.


==== constructorWithIncompleteTypeAnnotation.ts (93 errors) ====
!!! error TS-1: Pre-emit (92) and post-emit (93) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here!
!!! related TS-1: The excess diagnostics are:
!!! related TS7017 constructorWithIncompleteTypeAnnotation.ts:239:29: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
==== constructorWithIncompleteTypeAnnotation.ts (92 errors) ====
declare module "fs" {
export class File {
constructor(filename: string);
Expand All @@ -107,8 +110,6 @@ constructorWithIncompleteTypeAnnotation.ts(261,1): error TS1128: Declaration or
import fs = module("fs");
~~~~~~
!!! error TS2503: Cannot find namespace 'module'.
~~~~~~
!!! error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
~
!!! error TS1005: ';' expected.

Expand Down
Loading
Loading