or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

builtin-types.mdindex.mdproperty-types.mdsymbol-analysis.mdtype-analysis.mdtype-constraints.mdtype-predicates.mdtype-safety.mdtype-specifiers.md

type-safety.mddocs/

0

# Type Safety Utilities

1

2

Functions for detecting potentially unsafe type operations, particularly around readonly properties and any type assignments. These utilities help identify code patterns that may lead to runtime errors or violate type safety guarantees.

3

4

## Capabilities

5

6

### Readonly Type Analysis

7

8

Functions for analyzing whether types and their properties are readonly, crucial for understanding mutability guarantees.

9

10

```typescript { .api }

11

interface ReadonlynessOptions {

12

readonly allow?: TypeOrValueSpecifier[];

13

readonly treatMethodsAsReadonly?: boolean;

14

}

15

16

/**

17

* Checks if the given type is readonly. Handles arrays, tuples, objects,

18

* properties, index signatures, unions, intersections, and conditional types.

19

*/

20

function isTypeReadonly(

21

program: ts.Program,

22

type: ts.Type,

23

options?: ReadonlynessOptions

24

): boolean;

25

```

26

27

**Constants:**

28

29

```typescript { .api }

30

const readonlynessOptionsDefaults: ReadonlynessOptions;

31

const readonlynessOptionsSchema: JSONSchema4;

32

```

33

34

**Usage Examples:**

35

36

```typescript

37

import { isTypeReadonly, readonlynessOptionsDefaults } from "@typescript-eslint/type-utils";

38

39

// In an ESLint rule checking for readonly violations

40

export default {

41

create(context) {

42

const services = context.parserServices;

43

const program = services.program;

44

45

return {

46

AssignmentExpression(node) {

47

if (node.left.type === "MemberExpression") {

48

const tsNode = services.esTreeNodeToTSNodeMap.get(node.left.object);

49

const type = program.getTypeChecker().getTypeAtLocation(tsNode);

50

51

if (isTypeReadonly(program, type)) {

52

context.report({

53

node,

54

messageId: "readonlyViolation"

55

});

56

}

57

}

58

},

59

60

CallExpression(node) {

61

// Check method calls on readonly types

62

if (node.callee.type === "MemberExpression") {

63

const tsObject = services.esTreeNodeToTSNodeMap.get(node.callee.object);

64

const objectType = program.getTypeChecker().getTypeAtLocation(tsObject);

65

66

const options = {

67

...readonlynessOptionsDefaults,

68

treatMethodsAsReadonly: true

69

};

70

71

if (isTypeReadonly(program, objectType, options)) {

72

// Check if this method mutates the object

73

const methodName = node.callee.property.name;

74

if (['push', 'pop', 'shift', 'unshift', 'splice'].includes(methodName)) {

75

context.report({

76

node,

77

messageId: "mutatingMethodOnReadonly"

78

});

79

}

80

}

81

}

82

}

83

};

84

}

85

};

86

```

87

88

### Unsafe Assignment Detection

89

90

Functions for detecting unsafe assignments, particularly those involving `any` types that could bypass TypeScript's type checking.

91

92

```typescript { .api }

93

/**

94

* Checks if there's an unsafe assignment of `any` to a non-`any` type.

95

* Also checks generic positions for unsafe sub-assignments.

96

* @returns false if safe, or an object with the two types if unsafe

97

*/

98

function isUnsafeAssignment(

99

type: ts.Type,

100

receiver: ts.Type,

101

checker: ts.TypeChecker,

102

senderNode: TSESTree.Node | null

103

): false | { receiver: ts.Type; sender: ts.Type };

104

```

105

106

**Usage Examples:**

107

108

```typescript

109

import { isUnsafeAssignment } from "@typescript-eslint/type-utils";

110

111

// In an ESLint rule detecting unsafe assignments

112

export default {

113

create(context) {

114

const services = context.parserServices;

115

const checker = services.program.getTypeChecker();

116

117

return {

118

AssignmentExpression(node) {

119

const leftTsNode = services.esTreeNodeToTSNodeMap.get(node.left);

120

const rightTsNode = services.esTreeNodeToTSNodeMap.get(node.right);

121

122

const leftType = checker.getTypeAtLocation(leftTsNode);

123

const rightType = checker.getTypeAtLocation(rightTsNode);

124

125

const unsafeAssignment = isUnsafeAssignment(rightType, leftType, checker, node.right);

126

127

if (unsafeAssignment) {

128

context.report({

129

node,

130

messageId: "unsafeAnyAssignment",

131

data: {

132

sender: checker.typeToString(unsafeAssignment.sender),

133

receiver: checker.typeToString(unsafeAssignment.receiver)

134

}

135

});

136

}

137

},

138

139

VariableDeclarator(node) {

140

if (node.init && node.id.typeAnnotation) {

141

const initTsNode = services.esTreeNodeToTSNodeMap.get(node.init);

142

const idTsNode = services.esTreeNodeToTSNodeMap.get(node.id);

143

144

const initType = checker.getTypeAtLocation(initTsNode);

145

const declaredType = checker.getTypeAtLocation(idTsNode);

146

147

const unsafeAssignment = isUnsafeAssignment(initType, declaredType, checker, node.init);

148

149

if (unsafeAssignment) {

150

context.report({

151

node: node.init,

152

messageId: "unsafeAnyInitialization"

153

});

154

}

155

}

156

}

157

};

158

}

159

};

160

```

161

162

## Advanced Safety Analysis Patterns

163

164

### Readonly Options Configuration

165

166

```typescript

167

// Example: Configuring readonly analysis with custom options

168

import { isTypeReadonly, TypeOrValueSpecifier } from "@typescript-eslint/type-utils";

169

170

const customReadonlyOptions = {

171

allow: [

172

// Allow specific types to be treated as non-readonly

173

{ from: 'lib', name: 'Array' },

174

{ from: 'package', name: 'MutableArray', package: 'custom-utils' }

175

] as TypeOrValueSpecifier[],

176

treatMethodsAsReadonly: false

177

};

178

179

function checkCustomReadonly(program: ts.Program, type: ts.Type): boolean {

180

return isTypeReadonly(program, type, customReadonlyOptions);

181

}

182

```

183

184

### Generic Unsafe Assignment Detection

185

186

```typescript

187

// Example: Detecting unsafe assignments in generic contexts

188

import { isUnsafeAssignment } from "@typescript-eslint/type-utils";

189

190

function analyzeGenericAssignment(

191

services: ParserServicesWithTypeInformation,

192

node: TSESTree.CallExpression

193

) {

194

const checker = services.program.getTypeChecker();

195

196

// Check each argument for unsafe assignments

197

node.arguments.forEach((arg, index) => {

198

const argTsNode = services.esTreeNodeToTSNodeMap.get(arg);

199

const argType = checker.getTypeAtLocation(argTsNode);

200

201

// Get the expected parameter type

202

const callTsNode = services.esTreeNodeToTSNodeMap.get(node);

203

const signature = checker.getResolvedSignature(callTsNode);

204

205

if (signature && signature.parameters[index]) {

206

const paramType = checker.getTypeOfSymbolAtLocation(

207

signature.parameters[index],

208

callTsNode

209

);

210

211

const unsafeAssignment = isUnsafeAssignment(argType, paramType, checker, arg);

212

213

if (unsafeAssignment) {

214

console.log(`Unsafe assignment in argument ${index}`);

215

}

216

}

217

});

218

}

219

```

220

221

### Complex Readonly Analysis

222

223

```typescript

224

// Example: Deep readonly analysis for nested structures

225

import { isTypeReadonly } from "@typescript-eslint/type-utils";

226

227

function analyzeNestedReadonly(

228

program: ts.Program,

229

type: ts.Type,

230

checker: ts.TypeChecker

231

): { isReadonly: boolean; issues: string[] } {

232

const issues: string[] = [];

233

234

// Check top-level readonly

235

const isTopLevelReadonly = isTypeReadonly(program, type);

236

237

if (!isTopLevelReadonly) {

238

issues.push("Top-level type is not readonly");

239

}

240

241

// Check properties for nested structures

242

const properties = checker.getPropertiesOfType(type);

243

244

properties.forEach(prop => {

245

const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration!);

246

247

if (!isTypeReadonly(program, propType)) {

248

issues.push(`Property '${prop.name}' is not readonly`);

249

}

250

});

251

252

return {

253

isReadonly: isTopLevelReadonly && issues.length === 1, // Only top-level issue

254

issues

255

};

256

}

257

```

258

259

### Safe Assignment Validation

260

261

```typescript

262

// Example: Comprehensive assignment safety checking

263

import { isUnsafeAssignment, isTypeReadonly } from "@typescript-eslint/type-utils";

264

265

function validateAssignmentSafety(

266

services: ParserServicesWithTypeInformation,

267

assignment: TSESTree.AssignmentExpression

268

): { safe: boolean; issues: string[] } {

269

const checker = services.program.getTypeChecker();

270

const program = services.program;

271

const issues: string[] = [];

272

273

const leftTsNode = services.esTreeNodeToTSNodeMap.get(assignment.left);

274

const rightTsNode = services.esTreeNodeToTSNodeMap.get(assignment.right);

275

276

const leftType = checker.getTypeAtLocation(leftTsNode);

277

const rightType = checker.getTypeAtLocation(rightTsNode);

278

279

// Check for unsafe any assignments

280

const unsafeAssignment = isUnsafeAssignment(rightType, leftType, checker, assignment.right);

281

if (unsafeAssignment) {

282

issues.push("Unsafe any assignment detected");

283

}

284

285

// Check readonly violations

286

if (assignment.left.type === "MemberExpression") {

287

const objectTsNode = services.esTreeNodeToTSNodeMap.get(assignment.left.object);

288

const objectType = checker.getTypeAtLocation(objectTsNode);

289

290

if (isTypeReadonly(program, objectType)) {

291

issues.push("Assignment to readonly property");

292

}

293

}

294

295

return {

296

safe: issues.length === 0,

297

issues

298

};

299

}

300

```