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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion microsoft/typescript-go
Submodule typescript-go updated 479 files
18 changes: 18 additions & 0 deletions pkg/api/encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ func TestEncodeSourceFile(t *testing.T) {
})
}

func TestEncodeSourceFileWithUnicodeEscapes(t *testing.T) {
t.Parallel()
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
FileName: "/test.ts",
Path: "/test.ts",
}, `let a = "😃"; let b = "\ud83d\ude03"; let c = "\udc00\ud83d\ude03"; let d = "\ud83d\ud83d\ude03"`, core.ScriptKindTS)
t.Run("baseline", func(t *testing.T) {
t.Parallel()
buf, err := encoder.EncodeSourceFile(sourceFile, "")
assert.NilError(t, err)

str := formatEncodedSourceFile(buf)
baseline.Run(t, "encodeSourceFileWithUnicodeEscapes.txt", str, baseline.Options{
Subfolder: "api",
})
})
}

func BenchmarkEncodeSourceFile(b *testing.B) {
repo.SkipIfNoTypeScriptSubmodule(b)
filePath := filepath.Join(repo.TypeScriptSubmodulePath, "src/compiler/checker.ts")
Expand Down
9 changes: 9 additions & 0 deletions pkg/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,15 @@ func (n *Node) Statements() []*Node {
return nil
}

func (n *Node) CanHaveStatements() bool {
switch n.Kind {
case KindSourceFile, KindBlock, KindModuleBlock, KindCaseClause, KindDefaultClause:
return true
default:
return false
}
}

func (n *Node) ModifierFlags() ModifierFlags {
modifiers := n.Modifiers()
if modifiers != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/ast/nodeflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
NodeFlagsInWithStatement NodeFlags = 1 << 24 // If any ancestor of node was the `statement` of a WithStatement (not the `expression`)
NodeFlagsJsonFile NodeFlags = 1 << 25 // If node was parsed in a Json
NodeFlagsDeprecated NodeFlags = 1 << 26 // If has '@deprecated' JSDoc tag
NodeFlagsUnreachable NodeFlags = 1 << 27 // If node is unreachable according to the binder

NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing
NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing
Expand Down
24 changes: 24 additions & 0 deletions pkg/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2336,6 +2336,12 @@ func getModuleInstanceStateForAliasTarget(node *Node, ancestors []*Node, visited
return ModuleInstanceStateInstantiated
}

func IsInstantiatedModule(node *Node, preserveConstEnums bool) bool {
moduleState := GetModuleInstanceState(node)
return moduleState == ModuleInstanceStateInstantiated ||
(preserveConstEnums && moduleState == ModuleInstanceStateConstEnumOnly)
}

func NodeHasName(statement *Node, id *Node) bool {
name := statement.Name()
if name != nil {
Expand Down Expand Up @@ -3832,3 +3838,21 @@ func GetFirstConstructorWithBody(node *Node) *Node {
}
return nil
}

// Returns true for nodes that are considered executable for the purposes of unreachable code detection.
func IsPotentiallyExecutableNode(node *Node) bool {
if KindFirstStatement <= node.Kind && node.Kind <= KindLastStatement {
if IsVariableStatement(node) {
declarationList := node.AsVariableStatement().DeclarationList
if GetCombinedNodeFlags(declarationList)&NodeFlagsBlockScoped != 0 {
return true
}
declarations := declarationList.AsVariableDeclarationList().Declarations.Nodes
return core.Some(declarations, func(d *Node) bool {
return d.Initializer() != nil
})
}
return true
}
return IsClassDeclaration(node) || IsEnumDeclaration(node) || IsModuleDeclaration(node)
}
132 changes: 19 additions & 113 deletions pkg/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ const (
)

type Binder struct {
file *ast.SourceFile
bindFunc func(*ast.Node) bool
unreachableFlow *ast.FlowNode
reportedUnreachableFlow *ast.FlowNode
file *ast.SourceFile
bindFunc func(*ast.Node) bool
unreachableFlow *ast.FlowNode

container *ast.Node
thisContainer *ast.Node
Expand Down Expand Up @@ -122,7 +121,6 @@ func bindSourceFile(file *ast.SourceFile) {
b.file = file
b.inStrictMode = b.options().BindInStrictMode && !file.IsDeclarationFile || ast.IsExternalModule(file)
b.unreachableFlow = b.newFlowNode(ast.FlowFlagsUnreachable)
b.reportedUnreachableFlow = b.newFlowNode(ast.FlowFlagsUnreachable)
b.bind(file.AsNode())
file.SymbolCount = b.symbolCount
file.ClassifiableNames = b.classifiableNames
Expand Down Expand Up @@ -1535,18 +1533,25 @@ func (b *Binder) bindChildren(node *ast.Node) {
// Most nodes aren't valid in an assignment pattern, so we clear the value here
// and set it before we descend into nodes that could actually be part of an assignment pattern.
b.inAssignmentPattern = false
if b.checkUnreachable(node) {

if b.currentFlow == b.unreachableFlow {
if flowNodeData := node.FlowNodeData(); flowNodeData != nil {
flowNodeData.FlowNode = nil
}
if ast.IsPotentiallyExecutableNode(node) {
node.Flags |= ast.NodeFlagsUnreachable
}
b.bindEachChild(node)
b.inAssignmentPattern = saveInAssignmentPattern
return
}
kind := node.Kind
if kind >= ast.KindFirstStatement && kind <= ast.KindLastStatement && (b.options().AllowUnreachableCode != core.TSTrue || kind == ast.KindReturnStatement) {
hasFlowNodeData := node.FlowNodeData()
if hasFlowNodeData != nil {
hasFlowNodeData.FlowNode = b.currentFlow

if ast.KindFirstStatement <= node.Kind && node.Kind <= ast.KindLastStatement {
if flowNodeData := node.FlowNodeData(); flowNodeData != nil {
flowNodeData.FlowNode = b.currentFlow
}
}

switch node.Kind {
case ast.KindWhileStatement:
b.bindWhileStatement(node)
Expand Down Expand Up @@ -1657,94 +1662,6 @@ func (b *Binder) bindEachStatementFunctionsFirst(statements *ast.NodeList) {
}
}

func (b *Binder) checkUnreachable(node *ast.Node) bool {
if b.currentFlow.Flags&ast.FlowFlagsUnreachable == 0 {
return false
}
if b.currentFlow == b.unreachableFlow {
// report errors on all statements except empty ones
// report errors on class declarations
// report errors on enums with preserved emit
// report errors on instantiated modules
reportError := ast.IsStatementButNotDeclaration(node) && !ast.IsEmptyStatement(node) ||
ast.IsClassDeclaration(node) ||
isEnumDeclarationWithPreservedEmit(node, b.options()) ||
ast.IsModuleDeclaration(node) && b.shouldReportErrorOnModuleDeclaration(node)
if reportError {
b.currentFlow = b.reportedUnreachableFlow
if b.options().AllowUnreachableCode != core.TSTrue {
// unreachable code is reported if
// - user has explicitly asked about it AND
// - statement is in not ambient context (statements in ambient context is already an error
// so we should not report extras) AND
// - node is not variable statement OR
// - node is block scoped variable statement OR
// - node is not block scoped variable statement and at least one variable declaration has initializer
// Rationale: we don't want to report errors on non-initialized var's since they are hoisted
// On the other side we do want to report errors on non-initialized 'lets' because of TDZ
isError := unreachableCodeIsError(b.options()) && node.Flags&ast.NodeFlagsAmbient == 0 && (!ast.IsVariableStatement(node) ||
ast.GetCombinedNodeFlags(node.AsVariableStatement().DeclarationList)&ast.NodeFlagsBlockScoped != 0 ||
core.Some(node.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes, func(d *ast.Node) bool {
return d.Initializer() != nil
}))
b.errorOnEachUnreachableRange(node, isError)
}
}
}
return true
}

func (b *Binder) shouldReportErrorOnModuleDeclaration(node *ast.Node) bool {
instanceState := ast.GetModuleInstanceState(node)
return instanceState == ast.ModuleInstanceStateInstantiated || (instanceState == ast.ModuleInstanceStateConstEnumOnly && b.options().ShouldPreserveConstEnums)
}

func (b *Binder) errorOnEachUnreachableRange(node *ast.Node, isError bool) {
if b.isExecutableStatement(node) && ast.IsBlock(node.Parent) {
statements := node.Parent.Statements()
index := slices.Index(statements, node)
var first, last *ast.Node
for _, s := range statements[index:] {
if b.isExecutableStatement(s) {
if first == nil {
first = s
}
last = s
} else if first != nil {
b.errorOrSuggestionOnRange(isError, first, last, diagnostics.Unreachable_code_detected)
first = nil
}
}
if first != nil {
b.errorOrSuggestionOnRange(isError, first, last, diagnostics.Unreachable_code_detected)
}
} else {
b.errorOrSuggestionOnNode(isError, node, diagnostics.Unreachable_code_detected)
}
}

// As opposed to a pure declaration like an `interface`
func (b *Binder) isExecutableStatement(s *ast.Node) bool {
// Don't remove statements that can validly be used before they appear.
return !ast.IsFunctionDeclaration(s) && !b.isPurelyTypeDeclaration(s) && !(ast.IsVariableStatement(s) && ast.GetCombinedNodeFlags(s)&ast.NodeFlagsBlockScoped == 0 &&
core.Some(s.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes, func(d *ast.Node) bool {
return d.Initializer() == nil
}))
}

func (b *Binder) isPurelyTypeDeclaration(s *ast.Node) bool {
switch s.Kind {
case ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration:
return true
case ast.KindModuleDeclaration:
return ast.GetModuleInstanceState(s) != ast.ModuleInstanceStateInstantiated
case ast.KindEnumDeclaration:
return !isEnumDeclarationWithPreservedEmit(s, b.options())
default:
return false
}
}

func (b *Binder) setContinueTarget(node *ast.Node, target *ast.FlowLabel) *ast.FlowLabel {
label := b.activeLabelList
for label != nil && node.Parent.Kind == ast.KindLabeledStatement {
Expand Down Expand Up @@ -2131,8 +2048,9 @@ func (b *Binder) bindLabeledStatement(node *ast.Node) {
}
b.bind(stmt.Label)
b.bind(stmt.Statement)
if !b.activeLabelList.referenced && b.options().AllowUnusedLabels != core.TSTrue {
b.errorOrSuggestionOnNode(unusedLabelIsError(b.options()), stmt.Label, diagnostics.Unused_label)
if !b.activeLabelList.referenced {
// Mark the label as unused; the checker will decide whether to report it
stmt.Label.Flags |= ast.NodeFlagsUnreachable
}
b.activeLabelList = b.activeLabelList.next
b.addAntecedent(postStatementLabel, b.currentFlow)
Expand Down Expand Up @@ -2454,10 +2372,6 @@ func (b *Binder) bindInitializer(node *ast.Node) {
b.currentFlow = b.finishFlowLabel(exitFlow)
}

func isEnumDeclarationWithPreservedEmit(node *ast.Node, options core.SourceFileAffectingCompilerOptions) bool {
return node.Kind == ast.KindEnumDeclaration && (!ast.IsEnumConst(node) || options.ShouldPreserveConstEnums)
}

func setFlowNode(node *ast.Node, flowNode *ast.FlowNode) {
data := node.FlowNodeData()
if data != nil {
Expand Down Expand Up @@ -2749,14 +2663,6 @@ func isFunctionSymbol(symbol *ast.Symbol) bool {
return false
}

func unreachableCodeIsError(options core.SourceFileAffectingCompilerOptions) bool {
return options.AllowUnreachableCode == core.TSFalse
}

func unusedLabelIsError(options core.SourceFileAffectingCompilerOptions) bool {
return options.AllowUnusedLabels == core.TSFalse
}

func isStatementCondition(node *ast.Node) bool {
switch node.Parent.Kind {
case ast.KindIfStatement, ast.KindWhileStatement, ast.KindDoStatement:
Expand Down
Loading