or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-traversal.mdcode-analysis.mdcontrol-flow.mdindex.mdnode-typeguards.mdtext-processing.mdtype-guards.mdtype-utilities.mdvariable-usage.md

control-flow.mddocs/

0

# Control Flow Analysis

1

2

Control flow analysis utilities for determining statement reachability, analyzing function signatures, and identifying control flow termination points. These functions help understand code execution paths and unreachable code detection.

3

4

## Capabilities

5

6

### Control Flow Termination Analysis

7

8

Utilities for determining if statements end control flow execution.

9

10

```typescript { .api }

11

/**

12

* Check if statement or block ends control flow

13

* @param statement - Statement or block to analyze

14

* @param checker - Optional type checker for advanced analysis

15

* @returns true if statement terminates control flow

16

*/

17

function endsControlFlow(statement: ts.Statement | ts.BlockLike, checker?: ts.TypeChecker): boolean;

18

19

/**

20

* Get detailed control flow end information

21

* @param statement - Statement or block to analyze

22

* @param checker - Optional type checker for advanced analysis

23

* @returns Control flow end details

24

*/

25

function getControlFlowEnd(statement: ts.Statement | ts.BlockLike, checker?: ts.TypeChecker): ControlFlowEnd;

26

27

/**

28

* Control flow end result interface

29

*/

30

interface ControlFlowEnd {

31

/** Array of statements that end control flow */

32

readonly statements: ReadonlyArray<ControlFlowStatement>;

33

/** Whether control flow definitely ends */

34

readonly end: boolean;

35

}

36

37

/**

38

* Types of statements that can end control flow

39

*/

40

type ControlFlowStatement = ts.BreakStatement

41

| ts.ContinueStatement

42

| ts.ReturnStatement

43

| ts.ThrowStatement

44

| ts.ExpressionStatement & {expression: ts.CallExpression};

45

```

46

47

### Function Signature Analysis

48

49

Utilities for analyzing function call effects on control flow.

50

51

```typescript { .api }

52

/**

53

* Check if call expression affects control flow

54

* @param node - Call expression to analyze

55

* @param checker - Type checker instance

56

* @returns Signature effect if call affects control flow

57

*/

58

function callExpressionAffectsControlFlow(

59

node: ts.CallExpression,

60

checker: ts.TypeChecker

61

): SignatureEffect | undefined;

62

63

/**

64

* Function signature effects on control flow

65

*/

66

enum SignatureEffect {

67

/** Function never returns (throws or infinite loop) */

68

Never = 1,

69

/** Function performs assertions that may throw */

70

Asserts = 2

71

}

72

```

73

74

**Usage Examples:**

75

76

```typescript

77

import * as ts from "typescript";

78

import {

79

endsControlFlow,

80

getControlFlowEnd,

81

callExpressionAffectsControlFlow,

82

SignatureEffect

83

} from "tsutils/util";

84

85

// Basic control flow analysis

86

function analyzeControlFlow(statement: ts.Statement) {

87

if (endsControlFlow(statement)) {

88

console.log("Statement ends control flow");

89

90

const details = getControlFlowEnd(statement);

91

console.log(`Control flow ends: ${details.end}`);

92

console.log(`Terminating statements: ${details.statements.length}`);

93

94

details.statements.forEach((stmt, index) => {

95

console.log(` ${index}: ${ts.SyntaxKind[stmt.kind]}`);

96

});

97

}

98

}

99

100

// Function call analysis

101

function analyzeFunctionCalls(checker: ts.TypeChecker, callExpr: ts.CallExpression) {

102

const effect = callExpressionAffectsControlFlow(callExpr, checker);

103

104

if (effect !== undefined) {

105

switch (effect) {

106

case SignatureEffect.Never:

107

console.log("Function never returns");

108

break;

109

case SignatureEffect.Asserts:

110

console.log("Function performs assertions");

111

break;

112

}

113

}

114

}

115

116

// Unreachable code detection

117

function detectUnreachableCode(block: ts.Block) {

118

for (let i = 0; i < block.statements.length; i++) {

119

const statement = block.statements[i];

120

121

if (endsControlFlow(statement)) {

122

// Check if there are statements after this one

123

if (i < block.statements.length - 1) {

124

console.log(`Unreachable code detected after statement at index ${i}`);

125

126

// Analyze what statements are unreachable

127

for (let j = i + 1; j < block.statements.length; j++) {

128

const unreachableStmt = block.statements[j];

129

console.log(` Unreachable: ${ts.SyntaxKind[unreachableStmt.kind]}`);

130

}

131

}

132

break;

133

}

134

}

135

}

136

137

// Advanced control flow analysis with type checker

138

function analyzeAdvancedControlFlow(checker: ts.TypeChecker, node: ts.Node) {

139

node.forEachChild(child => {

140

if (ts.isStatement(child)) {

141

const details = getControlFlowEnd(child, checker);

142

143

if (details.end) {

144

console.log(`Statement ends control flow: ${ts.SyntaxKind[child.kind]}`);

145

146

// Analyze the specific terminating statements

147

details.statements.forEach(stmt => {

148

if (ts.isReturnStatement(stmt)) {

149

console.log(" Ends with return");

150

} else if (ts.isThrowStatement(stmt)) {

151

console.log(" Ends with throw");

152

} else if (ts.isBreakStatement(stmt)) {

153

console.log(" Ends with break");

154

} else if (ts.isContinueStatement(stmt)) {

155

console.log(" Ends with continue");

156

} else if (ts.isExpressionStatement(stmt) && ts.isCallExpression(stmt.expression)) {

157

// Check if call never returns

158

const effect = callExpressionAffectsControlFlow(stmt.expression, checker);

159

if (effect === SignatureEffect.Never) {

160

console.log(" Ends with never-returning function call");

161

}

162

}

163

});

164

}

165

}

166

167

analyzeAdvancedControlFlow(checker, child);

168

});

169

}

170

171

// Switch statement analysis

172

function analyzeSwitchControlFlow(switchStmt: ts.SwitchStatement, checker?: ts.TypeChecker) {

173

let hasDefaultCase = false;

174

let allCasesEndControlFlow = true;

175

176

switchStmt.caseBlock.clauses.forEach(clause => {

177

if (ts.isDefaultClause(clause)) {

178

hasDefaultCase = true;

179

}

180

181

// Check if this case ends control flow

182

const statements = clause.statements;

183

if (statements.length === 0) {

184

// Empty case falls through

185

allCasesEndControlFlow = false;

186

} else {

187

const lastStatement = statements[statements.length - 1];

188

if (!endsControlFlow(lastStatement, checker)) {

189

allCasesEndControlFlow = false;

190

}

191

}

192

});

193

194

if (hasDefaultCase && allCasesEndControlFlow) {

195

console.log("Switch statement is exhaustive and all cases end control flow");

196

}

197

}

198

199

// If-else chain analysis

200

function analyzeIfElseControlFlow(ifStmt: ts.IfStatement, checker?: ts.TypeChecker) {

201

let allBranchesEndControlFlow = true;

202

203

// Check then branch

204

if (!endsControlFlow(ifStmt.thenStatement, checker)) {

205

allBranchesEndControlFlow = false;

206

}

207

208

// Check else branch

209

if (ifStmt.elseStatement) {

210

if (!endsControlFlow(ifStmt.elseStatement, checker)) {

211

allBranchesEndControlFlow = false;

212

}

213

} else {

214

// No else branch means control flow can continue

215

allBranchesEndControlFlow = false;

216

}

217

218

if (allBranchesEndControlFlow) {

219

console.log("All branches of if-else end control flow");

220

}

221

}

222

223

// Try-catch-finally analysis

224

function analyzeTryCatchControlFlow(tryStmt: ts.TryStatement, checker?: ts.TypeChecker) {

225

const tryEnds = endsControlFlow(tryStmt.tryBlock, checker);

226

let catchEnds = true;

227

228

if (tryStmt.catchClause) {

229

catchEnds = endsControlFlow(tryStmt.catchClause.block, checker);

230

}

231

232

if (tryStmt.finallyBlock) {

233

const finallyEnds = endsControlFlow(tryStmt.finallyBlock, checker);

234

if (finallyEnds) {

235

console.log("Finally block ends control flow (overrides try/catch)");

236

}

237

}

238

239

if (tryEnds && catchEnds) {

240

console.log("Both try and catch blocks end control flow");

241

}

242

}

243

244

// Dead code elimination helper

245

function identifyDeadCode(sourceFile: ts.SourceFile, checker: ts.TypeChecker) {

246

const deadCodeRanges: ts.TextRange[] = [];

247

248

function visitNode(node: ts.Node) {

249

if (ts.isBlock(node)) {

250

for (let i = 0; i < node.statements.length; i++) {

251

const statement = node.statements[i];

252

253

if (endsControlFlow(statement, checker)) {

254

// Mark remaining statements as dead code

255

for (let j = i + 1; j < node.statements.length; j++) {

256

const deadStatement = node.statements[j];

257

deadCodeRanges.push({

258

pos: deadStatement.pos,

259

end: deadStatement.end

260

});

261

}

262

break;

263

}

264

}

265

}

266

267

node.forEachChild(visitNode);

268

}

269

270

visitNode(sourceFile);

271

return deadCodeRanges;

272

}

273

```

274

275

**Common Use Cases:**

276

277

1. **Unreachable Code Detection**: Identify code that can never be executed

278

2. **Dead Code Elimination**: Remove statements after control flow termination

279

3. **Control Flow Graph Construction**: Build flow graphs for analysis

280

4. **Switch Statement Exhaustiveness**: Verify all code paths are handled

281

5. **Return Path Analysis**: Ensure all function paths return values

282

6. **Exception Flow Analysis**: Track throw statements and exception propagation

283

7. **Loop Termination Analysis**: Detect infinite loops and guaranteed exits

284

8. **Conditional Branch Analysis**: Analyze if-else chains and nested conditions

285

286

The control flow analysis utilities are essential for:

287

- Static analysis tools and linters

288

- Code optimization and dead code elimination

289

- Compiler warnings about unreachable code

290

- Code coverage analysis

291

- Refactoring tools that need to understand execution paths

292

- Security analysis tools that track data flow