1+ "use strict";
2+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+ if (k2 === undefined) k2 = k;
4+ var desc = Object.getOwnPropertyDescriptor(m, k);
5+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6+ desc = { enumerable: true, get: function() { return m[k]; } };
7+ }
8+ Object.defineProperty(o, k2, desc);
9+ }) : (function(o, m, k, k2) {
10+ if (k2 === undefined) k2 = k;
11+ o[k2] = m[k];
12+ }));
13+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15+ }) : function(o, v) {
16+ o["default"] = v;
17+ });
18+ var __importStar = (this && this.__importStar) || function (mod) {
19+ if (mod && mod.__esModule) return mod;
20+ var result = {};
21+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22+ __setModuleDefault(result, mod);
23+ return result;
24+ };
25+ Object.defineProperty(exports, "__esModule", { value: true });
26+ const utils_1 = require("@typescript-eslint/utils");
27+ const ts = __importStar(require("typescript"));
28+ const util = __importStar(require("../util"));
29+ var Usefulness;
30+ (function (Usefulness) {
31+ Usefulness[Usefulness["Always"] = 0] = "Always";
32+ Usefulness["Never"] = "will";
33+ Usefulness["Sometimes"] = "may";
34+ })(Usefulness || (Usefulness = {}));
35+ exports.default = util.createRule({
36+ name: 'no-base-to-string',
37+ meta: {
38+ docs: {
39+ description: 'Requires that `.toString()` is only called on objects which provide useful information when stringified',
40+ recommended: 'strict',
41+ requiresTypeChecking: true,
42+ },
43+ messages: {
44+ baseToString: "'{{name}} {{certainty}} evaluate to '[object Object]' when stringified.",
45+ },
46+ schema: [
47+ {
48+ type: 'object',
49+ properties: {
50+ ignoredTypeNames: {
51+ type: 'array',
52+ items: {
53+ type: 'string',
54+ },
55+ },
56+ },
57+ additionalProperties: false,
58+ },
59+ ],
60+ type: 'suggestion',
61+ },
62+ defaultOptions: [
63+ {
64+ ignoredTypeNames: ['RegExp'],
65+ },
66+ ],
67+ create(context, [option]) {
68+ var _a;
69+ const parserServices = util.getParserServices(context);
70+ const typeChecker = parserServices.program.getTypeChecker();
71+ const ignoredTypeNames = (_a = option.ignoredTypeNames) !== null && _a !== void 0 ? _a : [];
72+ function checkExpression(node, type) {
73+ if (node.type === utils_1.AST_NODE_TYPES.Literal) {
74+ return;
75+ }
76+ const certainty = collectToStringCertainty(type !== null && type !== void 0 ? type : typeChecker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node)));
77+ if (certainty === Usefulness.Always) {
78+ return;
79+ }
80+ context.report({
81+ data: {
82+ certainty,
83+ name: context.getSourceCode().getText(node),
84+ },
85+ messageId: 'baseToString',
86+ node,
87+ });
88+ }
89+ function collectToStringCertainty(type) {
90+ const toString = typeChecker.getPropertyOfType(type, 'toString');
91+ const declarations = toString === null || toString === void 0 ? void 0 : toString.getDeclarations();
92+ if (!toString || !declarations || declarations.length === 0) {
93+ return Usefulness.Always;
94+ }
95+ // Patch for old version TypeScript, the Boolean type definition missing toString()
96+ if (type.flags & ts.TypeFlags.Boolean ||
97+ type.flags & ts.TypeFlags.BooleanLiteral) {
98+ return Usefulness.Always;
99+ }
100+ if (ignoredTypeNames.includes(util.getTypeName(typeChecker, type))) {
101+ return Usefulness.Always;
102+ }
103+ if (declarations.every(({ parent }) => !ts.isInterfaceDeclaration(parent) || parent.name.text !== 'Object')) {
104+ return Usefulness.Always;
105+ }
106+ if (type.isIntersection()) {
107+ for (const subType of type.types) {
108+ const subtypeUsefulness = collectToStringCertainty(subType);
109+ if (subtypeUsefulness === Usefulness.Always) {
110+ return Usefulness.Always;
111+ }
112+ }
113+ return Usefulness.Never;
114+ }
115+ if (!type.isUnion()) {
116+ return Usefulness.Never;
117+ }
118+ let allSubtypesUseful = true;
119+ let someSubtypeUseful = false;
120+ for (const subType of type.types) {
121+ const subtypeUsefulness = collectToStringCertainty(subType);
122+ if (subtypeUsefulness !== Usefulness.Always && allSubtypesUseful) {
123+ allSubtypesUseful = false;
124+ }
125+ if (subtypeUsefulness !== Usefulness.Never && !someSubtypeUseful) {
126+ someSubtypeUseful = true;
127+ }
128+ }
129+ if (allSubtypesUseful && someSubtypeUseful) {
130+ return Usefulness.Always;
131+ }
132+ if (someSubtypeUseful) {
133+ return Usefulness.Sometimes;
134+ }
135+ return Usefulness.Never;
136+ }
137+ return {
138+ 'AssignmentExpression[operator = "+="], BinaryExpression[operator = "+"]'(node) {
139+ const leftType = typeChecker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node.left));
140+ const rightType = typeChecker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node.right));
141+ if (util.getTypeName(typeChecker, leftType) === 'string') {
142+ checkExpression(node.right, rightType);
143+ }
144+ else if (util.getTypeName(typeChecker, rightType) === 'string' &&
145+ node.left.type !== utils_1.AST_NODE_TYPES.PrivateIdentifier) {
146+ checkExpression(node.left, leftType);
147+ }
148+ },
149+ 'CallExpression > MemberExpression.callee > Identifier[name = "toString"].property'(node) {
150+ const memberExpr = node.parent;
151+ checkExpression(memberExpr.object);
152+ },
153+ TemplateLiteral(node) {
154+ if (node.parent &&
155+ node.parent.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression) {
156+ return;
157+ }
158+ for (const expression of node.expressions) {
159+ checkExpression(expression);
160+ }
161+ },
162+ };
163+ },
164+ });
165+ //# sourceMappingURL=no-base-to-string.js.map
0 commit comments