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

property-types.mddocs/

0

# Property Type Utilities

1

2

Functions for extracting and analyzing property types from complex type structures. These utilities help understand object properties, their types, and relationships within TypeScript's type system.

3

4

## Capabilities

5

6

### Property Type Extraction

7

8

Functions for getting property types from complex objects and type structures.

9

10

```typescript { .api }

11

/**

12

* Gets the type of a property by name, handling symbolic names correctly

13

*/

14

function getTypeOfPropertyOfName(

15

checker: ts.TypeChecker,

16

type: ts.Type,

17

name: string,

18

escapedName?: ts.__String

19

): ts.Type | undefined;

20

21

/**

22

* Gets the type of a property using a symbol

23

*/

24

function getTypeOfPropertyOfType(

25

checker: ts.TypeChecker,

26

type: ts.Type,

27

property: ts.Symbol

28

): ts.Type | undefined;

29

```

30

31

**Usage Examples:**

32

33

```typescript

34

import { getTypeOfPropertyOfName, getTypeOfPropertyOfType } from "@typescript-eslint/type-utils";

35

36

// In an ESLint rule analyzing object property access

37

export default {

38

create(context) {

39

const services = context.parserServices;

40

const checker = services.program.getTypeChecker();

41

42

return {

43

MemberExpression(node) {

44

if (node.computed === false && node.property.type === "Identifier") {

45

const objectTsNode = services.esTreeNodeToTSNodeMap.get(node.object);

46

const objectType = checker.getTypeAtLocation(objectTsNode);

47

48

// Get property type by name

49

const propertyType = getTypeOfPropertyOfName(

50

checker,

51

objectType,

52

node.property.name

53

);

54

55

if (propertyType) {

56

const propertyTypeName = checker.typeToString(propertyType);

57

console.log(`Property ${node.property.name} has type: ${propertyTypeName}`);

58

59

// Example: Check for specific property types

60

if (checker.typeToString(propertyType) === "any") {

61

context.report({

62

node: node.property,

63

messageId: "anyPropertyType"

64

});

65

}

66

} else {

67

context.report({

68

node: node.property,

69

messageId: "unknownProperty",

70

data: { propertyName: node.property.name }

71

});

72

}

73

}

74

},

75

76

TSPropertySignature(node) {

77

if (node.key.type === "Identifier") {

78

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

79

const symbol = checker.getSymbolAtLocation(tsNode);

80

81

if (symbol && symbol.parent) {

82

const parentType = checker.getTypeOfSymbolAtLocation(symbol.parent, tsNode);

83

84

// Get property type using symbol

85

const propertyType = getTypeOfPropertyOfType(checker, parentType, symbol);

86

87

if (propertyType) {

88

console.log(`Property symbol type: ${checker.typeToString(propertyType)}`);

89

}

90

}

91

}

92

}

93

};

94

}

95

};

96

```

97

98

## Advanced Property Analysis Patterns

99

100

### Object Property Analysis

101

102

```typescript

103

// Example: Comprehensive object property analysis

104

import { getTypeOfPropertyOfName, getTypeOfPropertyOfType } from "@typescript-eslint/type-utils";

105

106

interface PropertyInfo {

107

name: string;

108

type: string;

109

isOptional: boolean;

110

isReadonly: boolean;

111

symbol?: ts.Symbol;

112

}

113

114

function analyzeObjectProperties(

115

checker: ts.TypeChecker,

116

objectType: ts.Type

117

): PropertyInfo[] {

118

const properties: PropertyInfo[] = [];

119

const propertySymbols = checker.getPropertiesOfType(objectType);

120

121

propertySymbols.forEach(symbol => {

122

const propertyType = getTypeOfPropertyOfType(checker, objectType, symbol);

123

124

if (propertyType) {

125

const isOptional = (symbol.flags & ts.SymbolFlags.Optional) !== 0;

126

const isReadonly = symbol.valueDeclaration ?

127

ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Readonly : false;

128

129

properties.push({

130

name: symbol.name,

131

type: checker.typeToString(propertyType),

132

isOptional,

133

isReadonly: !!isReadonly,

134

symbol

135

});

136

}

137

});

138

139

return properties;

140

}

141

142

// Usage in ESLint rule

143

function validateObjectStructure(

144

services: ParserServicesWithTypeInformation,

145

objectLiteral: TSESTree.ObjectExpression

146

) {

147

const checker = services.program.getTypeChecker();

148

const tsNode = services.esTreeNodeToTSNodeMap.get(objectLiteral);

149

const objectType = checker.getTypeAtLocation(tsNode);

150

151

const properties = analyzeObjectProperties(checker, objectType);

152

153

properties.forEach(prop => {

154

if (prop.type === "any") {

155

console.log(`Property ${prop.name} has any type`);

156

}

157

158

if (!prop.isOptional && prop.type.includes("undefined")) {

159

console.log(`Required property ${prop.name} includes undefined`);

160

}

161

});

162

}

163

```

164

165

### Dynamic Property Access Analysis

166

167

```typescript

168

// Example: Analyzing dynamic property access patterns

169

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

170

171

function analyzeDynamicPropertyAccess(

172

services: ParserServicesWithTypeInformation,

173

memberExpression: TSESTree.MemberExpression

174

): {

175

isValidAccess: boolean;

176

propertyType: string | null;

177

suggestions: string[];

178

} {

179

const checker = services.program.getTypeChecker();

180

const objectTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);

181

const objectType = checker.getTypeAtLocation(objectTsNode);

182

183

let propertyName: string;

184

let isValidAccess = false;

185

let propertyType: string | null = null;

186

const suggestions: string[] = [];

187

188

if (memberExpression.computed && memberExpression.property.type === "Literal") {

189

// obj["propertyName"]

190

propertyName = String(memberExpression.property.value);

191

} else if (!memberExpression.computed && memberExpression.property.type === "Identifier") {

192

// obj.propertyName

193

propertyName = memberExpression.property.name;

194

} else {

195

return { isValidAccess: false, propertyType: null, suggestions: [] };

196

}

197

198

// Check if property exists

199

const type = getTypeOfPropertyOfName(checker, objectType, propertyName);

200

201

if (type) {

202

isValidAccess = true;

203

propertyType = checker.typeToString(type);

204

} else {

205

// Property doesn't exist, suggest similar properties

206

const allProperties = checker.getPropertiesOfType(objectType);

207

208

allProperties.forEach(symbol => {

209

const similarity = calculateSimilarity(propertyName, symbol.name);

210

if (similarity > 0.6) { // Basic similarity threshold

211

suggestions.push(symbol.name);

212

}

213

});

214

}

215

216

return { isValidAccess, propertyType, suggestions };

217

}

218

219

function calculateSimilarity(a: string, b: string): number {

220

// Simple similarity calculation (Levenshtein-based)

221

const longer = a.length > b.length ? a : b;

222

const shorter = a.length > b.length ? b : a;

223

224

if (longer.length === 0) return 1.0;

225

226

const distance = levenshteinDistance(longer, shorter);

227

return (longer.length - distance) / longer.length;

228

}

229

230

function levenshteinDistance(str1: string, str2: string): number {

231

const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));

232

233

for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;

234

for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;

235

236

for (let j = 1; j <= str2.length; j++) {

237

for (let i = 1; i <= str1.length; i++) {

238

const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;

239

matrix[j][i] = Math.min(

240

matrix[j][i - 1] + 1,

241

matrix[j - 1][i] + 1,

242

matrix[j - 1][i - 1] + indicator

243

);

244

}

245

}

246

247

return matrix[str2.length][str1.length];

248

}

249

```

250

251

### Nested Property Type Analysis

252

253

```typescript

254

// Example: Deep property type analysis for nested objects

255

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

256

257

interface NestedPropertyPath {

258

path: string[];

259

type: string;

260

depth: number;

261

}

262

263

function analyzeNestedProperties(

264

checker: ts.TypeChecker,

265

rootType: ts.Type,

266

maxDepth: number = 3

267

): NestedPropertyPath[] {

268

const paths: NestedPropertyPath[] = [];

269

270

function traverse(type: ts.Type, currentPath: string[], depth: number) {

271

if (depth >= maxDepth) return;

272

273

const properties = checker.getPropertiesOfType(type);

274

275

properties.forEach(symbol => {

276

const propertyType = getTypeOfPropertyOfName(checker, type, symbol.name);

277

278

if (propertyType) {

279

const newPath = [...currentPath, symbol.name];

280

const typeString = checker.typeToString(propertyType);

281

282

paths.push({

283

path: newPath,

284

type: typeString,

285

depth: depth + 1

286

});

287

288

// Recursively analyze object properties

289

const propertySymbols = checker.getPropertiesOfType(propertyType);

290

if (propertySymbols.length > 0 && depth < maxDepth - 1) {

291

traverse(propertyType, newPath, depth + 1);

292

}

293

}

294

});

295

}

296

297

traverse(rootType, [], 0);

298

return paths;

299

}

300

301

// Usage example

302

function validateNestedObjectAccess(

303

services: ParserServicesWithTypeInformation,

304

node: TSESTree.MemberExpression

305

) {

306

const checker = services.program.getTypeChecker();

307

308

// Build property access path

309

const path: string[] = [];

310

let current: TSESTree.Node = node;

311

312

while (current.type === "MemberExpression") {

313

if (!current.computed && current.property.type === "Identifier") {

314

path.unshift(current.property.name);

315

}

316

current = current.object;

317

}

318

319

// Get root object type

320

const rootTsNode = services.esTreeNodeToTSNodeMap.get(current);

321

const rootType = checker.getTypeAtLocation(rootTsNode);

322

323

// Validate each step in the path

324

let currentType = rootType;

325

326

for (const propertyName of path) {

327

const propertyType = getTypeOfPropertyOfName(checker, currentType, propertyName);

328

329

if (!propertyType) {

330

console.log(`Property ${propertyName} not found in path ${path.join('.')}`);

331

break;

332

}

333

334

currentType = propertyType;

335

}

336

}

337

```

338

339

### Index Signature Analysis

340

341

```typescript

342

// Example: Analyzing index signatures and dynamic properties

343

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

344

345

function analyzeIndexSignatures(

346

checker: ts.TypeChecker,

347

type: ts.Type

348

): {

349

hasStringIndex: boolean;

350

hasNumberIndex: boolean;

351

stringIndexType?: string;

352

numberIndexType?: string;

353

dynamicPropertyAccess: boolean;

354

} {

355

const stringIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.String);

356

const numberIndexType = checker.getIndexTypeOfType(type, ts.IndexKind.Number);

357

358

return {

359

hasStringIndex: !!stringIndexType,

360

hasNumberIndex: !!numberIndexType,

361

stringIndexType: stringIndexType ? checker.typeToString(stringIndexType) : undefined,

362

numberIndexType: numberIndexType ? checker.typeToString(numberIndexType) : undefined,

363

dynamicPropertyAccess: !!(stringIndexType || numberIndexType)

364

};

365

}

366

367

// Check if dynamic property access is safe

368

function validateDynamicAccess(

369

services: ParserServicesWithTypeInformation,

370

memberExpression: TSESTree.MemberExpression

371

) {

372

if (!memberExpression.computed) return;

373

374

const checker = services.program.getTypeChecker();

375

const objectTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.object);

376

const objectType = checker.getTypeAtLocation(objectTsNode);

377

378

const indexInfo = analyzeIndexSignatures(checker, objectType);

379

380

if (!indexInfo.dynamicPropertyAccess) {

381

console.log("Object doesn't support dynamic property access");

382

return;

383

}

384

385

// Validate property key type

386

const propertyTsNode = services.esTreeNodeToTSNodeMap.get(memberExpression.property);

387

const propertyType = checker.getTypeAtLocation(propertyTsNode);

388

const propertyTypeString = checker.typeToString(propertyType);

389

390

if (indexInfo.hasStringIndex && propertyTypeString !== "string") {

391

console.log("String index signature requires string key");

392

}

393

394

if (indexInfo.hasNumberIndex && propertyTypeString !== "number") {

395

console.log("Number index signature requires number key");

396

}

397

}

398

```