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
139 changes: 13 additions & 126 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import {
DeleteExpression,
DestructuringAssignment,
DiagnosticArguments,
DiagnosticCategory,
DiagnosticMessage,
DiagnosticRelatedInformation,
Diagnostics,
Expand Down Expand Up @@ -87,7 +86,6 @@ import {
getAssignmentDeclarationKind,
getAssignmentDeclarationPropertyAccessKind,
getCombinedModifierFlags,
getCombinedNodeFlags,
getContainingClass,
getEffectiveContainerForJSDocTemplateTag,
getElementOrPropertyAccessName,
Expand All @@ -105,7 +103,6 @@ import {
getNameOfDeclaration,
getNameOrArgument,
getNodeId,
getRangesWhere,
getRightMostAssignedExpression,
getSourceFileOfNode,
getSourceTextOfNodeFromSourceFile,
Expand All @@ -114,10 +111,8 @@ import {
getSymbolNameForPrivateIdentifier,
getTextOfIdentifierOrLiteral,
getThisContainer,
getTokenPosOfNode,
HasContainerFlags,
hasDynamicName,
HasFlowNode,
hasJSDocNodes,
HasLocals,
hasSyntacticModifier,
Expand Down Expand Up @@ -163,7 +158,6 @@ import {
isExternalModule,
isExternalOrCommonJsModule,
isForInOrOfStatement,
isFunctionDeclaration,
isFunctionLike,
isFunctionLikeDeclaration,
isFunctionLikeOrClassStaticBlockDeclaration,
Expand Down Expand Up @@ -203,6 +197,7 @@ import {
isParenthesizedExpression,
isPartOfParameterDeclaration,
isPartOfTypeQuery,
isPotentiallyExecutableNode,
isPrefixUnaryExpression,
isPrivateIdentifier,
isPrologueDirective,
Expand All @@ -216,8 +211,6 @@ import {
isSignedNumericLiteral,
isSourceFile,
isSpecialPropertyDeclaration,
isStatement,
isStatementButNotDeclaration,
isStatic,
isString,
isStringLiteralLike,
Expand Down Expand Up @@ -284,10 +277,8 @@ import {
setParentRecursive,
setValueDeclaration,
ShorthandPropertyAssignment,
shouldPreserveConstEnums,
SignatureDeclaration,
skipParentheses,
sliceAfter,
some,
SourceFile,
SpreadElement,
Expand All @@ -300,7 +291,6 @@ import {
symbolName,
SymbolTable,
SyntaxKind,
TextRange,
ThisExpression,
ThrowStatement,
tokenToString,
Expand All @@ -313,8 +303,6 @@ import {
TypeOfExpression,
TypeParameterDeclaration,
unescapeLeadingUnderscores,
unreachableCodeIsError,
unusedLabelIsError,
VariableDeclaration,
WhileStatement,
WithStatement,
Expand Down Expand Up @@ -562,7 +550,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
var classifiableNames: Set<__String>;

var unreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined);
var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined);
var bindBinaryExpressionFlow = createBindBinaryExpressionFlow();
/* eslint-enable no-var */

Expand All @@ -588,7 +575,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {

// Attach debugging information if necessary
Debug.attachFlowNodeDebugInfo(unreachableFlow);
Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow);

if (!file.locals) {
tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true);
Expand Down Expand Up @@ -1099,18 +1085,24 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
// 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.
inAssignmentPattern = false;
if (checkUnreachable(node)) {
if (canHaveFlowNode(node) && node.flowNode) {

if (currentFlow === unreachableFlow) {
if (canHaveFlowNode(node)) {
node.flowNode = undefined;
}
if (isPotentiallyExecutableNode(node)) {
(node as Mutable<Node>).flags |= NodeFlags.Unreachable;
}
bindEachChild(node);
bindJSDoc(node);
inAssignmentPattern = saveInAssignmentPattern;
return;
}
if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && (!options.allowUnreachableCode || node.kind === SyntaxKind.ReturnStatement)) {
(node as HasFlowNode).flowNode = currentFlow;

if (SyntaxKind.FirstStatement <= node.kind && node.kind <= SyntaxKind.LastStatement && canHaveFlowNode(node)) {
node.flowNode = currentFlow;
}

switch (node.kind) {
case SyntaxKind.WhileStatement:
bindWhileStatement(node as WhileStatement);
Expand Down Expand Up @@ -1788,8 +1780,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
};
bind(node.label);
bind(node.statement);
if (!activeLabelList.referenced && !options.allowUnusedLabels) {
errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label);
if (!activeLabelList.referenced) {
(node.label as Mutable<Node>).flags |= NodeFlags.Unreachable;
}
activeLabelList = activeLabelList.next;
addAntecedent(postStatementLabel, currentFlow);
Expand Down Expand Up @@ -2708,24 +2700,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, ...args));
}

function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void {
errorOrSuggestionOnRange(isError, node, node, message);
}

function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void {
addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message);
}

function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void {
const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message);
if (isError) {
file.bindDiagnostics.push(diag);
}
else {
file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion });
}
}

function bind(node: Node | undefined): void {
if (!node) {
return;
Expand Down Expand Up @@ -3757,93 +3731,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes);
}
}

// reachability checks

function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean {
const instanceState = getModuleInstanceState(node);
return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && shouldPreserveConstEnums(options));
}

function checkUnreachable(node: Node): boolean {
if (!(currentFlow.flags & FlowFlags.Unreachable)) {
return false;
}
if (currentFlow === unreachableFlow) {
const reportError =
// report error on all statements except empty ones
(isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) ||
// report error on class declarations
node.kind === SyntaxKind.ClassDeclaration ||
// report errors on enums with preserved emit
isEnumDeclarationWithPreservedEmit(node, options) ||
// report error on instantiated modules
(node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration));

if (reportError) {
currentFlow = reportedUnreachableFlow;

if (!options.allowUnreachableCode) {
// 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
const isError = unreachableCodeIsError(options) &&
!(node.flags & NodeFlags.Ambient) &&
(
!isVariableStatement(node) ||
!!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) ||
node.declarationList.declarations.some(d => !!d.initializer)
);

eachUnreachableRange(node, options, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected));
}
}
}
return true;
}
}

function isEnumDeclarationWithPreservedEmit(node: Node, options: CompilerOptions): boolean {
return node.kind === SyntaxKind.EnumDeclaration && (!isEnumConst(node as EnumDeclaration) || shouldPreserveConstEnums(options));
}

function eachUnreachableRange(node: Node, options: CompilerOptions, cb: (start: Node, last: Node) => void): void {
if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) {
const { statements } = node.parent;
const slice = sliceAfter(statements, node);
getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1]));
}
else {
cb(node, node);
}

// As opposed to a pure declaration like an `interface`
function isExecutableStatement(s: Statement): boolean {
// Don't remove statements that can validly be used before they appear.
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) &&
// `var x;` may declare a variable used above
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.BlockScoped)) && s.declarationList.declarations.some(d => !d.initializer));
}

function isPurelyTypeDeclaration(s: Statement): boolean {
switch (s.kind) {
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
return true;
case SyntaxKind.ModuleDeclaration:
return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated;
case SyntaxKind.EnumDeclaration:
return !isEnumDeclarationWithPreservedEmit(s, options);
default:
return false;
}
}
}

/** @internal */
Expand Down
Loading