如何用TSLint规则禁用TypeScript可索引类型改用Record类型
Hey there! Let's tackle your problem of enforcing Record types instead of indexable object types in TSLint.
First off, let's get this clear: TSLint doesn't have a built-in dedicated rule to directly detect and replace indexable types like { [key: string]: string } with Record<string, string>. Your idea to use the ban-types rule is totally reasonable, but regex configurations for this can be tricky—let's work through the issues and solid solutions:
一、 Fix the ban-types regex configuration
The escape errors or failed detection you ran into are almost certainly from the regex not properly matching TypeScript's indexable type syntax. Try this configuration in your tslint.yml to target string-indexed types:
rules: ban-types: - true - name: "{ [key: string]: * }" message: "Please use Record<string, T> instead of indexable types" regex: "^\\{\\s*\\[\\s*key\\s*:\\s*string\\s*\\]\\s*:\\s*.+\\s*\\}$"
This regex handles:
- Escaping curly braces and brackets (since they're special characters in both YAML and regex)
- Accommodating spaces in index signatures (works for both
[key:string]and[key: string]formats) - Matching any value type (via the
.+segment)
If your code uses index variable names other than key (like prop or field), swap key in the regex with \\w+ to match any identifier:
regex: "^\\{\\s*\\[\\s*\\w+\\s*:\\s*string\\s*\\]\\s*:\\s*.+\\s*\\}$"
That said, ban-types has clear limitations: it only matches rigidly formatted types. It won't handle nested indexable types, number-indexed types (like { [key: number]: T }), or mixed types with additional properties (e.g., { id: number; [key: string]: string }).
二、 A more robust solution: Custom TSLint rule
If ban-types isn't flexible enough, writing a custom TSLint rule is the way to go. Using TSLint's API and TypeScript's type checker, you can precisely identify all indexable types:
- Traverse the AST to find all
IndexSignatureDeclarationnodes (the index signature itself) - Check if the node belongs to an object type literal (
TypeLiteralNode) - Throw a lint error when detected, prompting a switch to
Record
Here's a simplified version of the rule code to reference:
import * as Lint from "tslint"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "Use Record<K, T> instead of indexable object types"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new IndexSignatureWalker(sourceFile, this.getOptions())); } } class IndexSignatureWalker extends Lint.RuleWalker { public visitIndexSignature(node: ts.IndexSignatureDeclaration) { // Verify the index signature is part of an object type literal if (ts.isTypeLiteralNode(node.parent)) { this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitIndexSignature(node); } }
This rule catches all index signatures (string or number indexed, with any variable name) reliably, which is way more consistent than regex matching.
三、 Bonus tip: Consider migrating to ESLint
One last note: TSLint is officially deprecated, and the TypeScript team now recommends ESLint + the @typescript-eslint plugin. If you plan to maintain your project long-term, migrating to ESLint lets you use similar logic—either configure @typescript-eslint/ban-types with a regex, or write a custom ESLint rule to achieve the same detection.
内容的提问来源于stack exchange,提问作者Tiu Wee Han

