UNPKG

tslint-etc

Version:
186 lines (185 loc) 7.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Rule = void 0; const Lint = require("tslint"); const tsutils = require("tsutils"); const ts = require("typescript"); const support_1 = require("../support"); const util_1 = require("../support/util"); const knownGlobalRegExp = /^(Array|BigInt|Date|Intl|JSON|Math|Number|Object|Promise|Proxy|Reflect|String|Symbol|console)$/; class Rule extends Lint.Rules.TypedRule { applyWithProgram(sourceFile, program) { return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program)); } } exports.Rule = Rule; Rule.metadata = { description: "Disallows the use of variables/properties from unsafe/outer scopes in callbacks.", options: { properties: { allowMethods: { type: "boolean" }, allowParameters: { type: "boolean" }, allowProperties: { type: "boolean" }, }, type: "object", }, optionsDescription: Lint.Utils.dedent ` An optional object with optional \`allowDo\`, \`allowParameters\` and \`allowTap\` properties all of which default to \`true\`. If the \`allowParameters\` option is \`true\`, referencing function parameters from outer scopes is allowed. If the \`allowMethods\` option is \`true\`, calling methods via \`this\` is allowed. If the \`allowProperties\` option is \`true\`, accessing properties via \`this\` is allowed.`, requiresTypeInfo: true, ruleName: "no-unsafe-callback-scope", type: "functionality", typescriptOnly: true, }; Rule.FAILURE_STRING = "Unsafe scopes are forbidden"; class Walker extends support_1.ScopeWalker { constructor(sourceFile, rawOptions, program) { super(sourceFile, rawOptions, program); this.allowMethods = true; this.allowParameters = true; this.allowProperties = false; const [options] = this.getOptions(); if (options) { this.allowMethods = options.allowMethods !== undefined ? options.allowMethods : this.allowMethods; this.allowParameters = options.allowParameters !== undefined ? options.allowParameters : this.allowParameters; this.allowProperties = options.allowProperties !== undefined ? options.allowProperties : this.allowProperties; } } visitNode(node) { if (this.callbackStack.length) { const validateNode = tsutils.isIdentifier(node) || util_1.isThis(node); if (validateNode) { const failureNode = this.isUnsafe(node); if (failureNode) { this.addFailureAtNode(failureNode, Rule.FAILURE_STRING); } } } super.visitNode(node); } isUnsafe(node) { const { callbackMap, callbackStack } = this; const leafCallback = callbackStack[callbackStack.length - 1]; const leafOperator = callbackMap.get(leafCallback); const rootCallback = callbackStack[0]; const typeChecker = this.getTypeChecker(); if (tsutils.isPropertyAccessExpression(node.parent)) { if (!isPropertyAccessExpressionLeaf(node)) { return undefined; } const declaration = util_1.findDeclaration(node, typeChecker); if (!declaration) { return undefined; } if (tsutils.hasModifier(declaration.modifiers, ts.SyntaxKind.ReadonlyKeyword)) { return undefined; } if (tsutils.isTypeFlagSet(typeChecker.getTypeAtLocation(node), ts.TypeFlags.EnumLiteral)) { return undefined; } const called = util_1.isWithinCallExpressionExpression(node); const root = getPropertyAccessExpressionRoot(node.parent); if (!root) { return undefined; } if (util_1.isThis(root)) { if (called) { return this.allowMethods ? undefined : root; } else { return this.allowProperties ? undefined : root; } } const rootText = root.getText(); if (knownGlobalRegExp.test(rootText)) { return undefined; } if (/^[A-Z]/.test(rootText)) { if (called) { return this.allowMethods ? undefined : root; } else { return this.allowProperties ? undefined : root; } } return this.isUnsafeRoot(root, rootCallback); } return this.isUnsafeRoot(node, rootCallback); } isUnsafeRoot(node, callback) { const typeChecker = this.getTypeChecker(); if (ts.isQualifiedName(node.parent)) { return undefined; } if (util_1.isInstanceofCtor(node)) { return undefined; } const declaration = util_1.findDeclaration(node, typeChecker); if (!declaration) { return undefined; } if (isWithinClosure(declaration, callback)) { return undefined; } if (this.allowParameters && util_1.isWithinParameterDeclaration(declaration)) { return undefined; } if (tsutils.isCallExpression(node.parent) && node === node.parent.expression) { return undefined; } if (tsutils.isTaggedTemplateExpression(node.parent) && node === node.parent.tag) { return undefined; } if (tsutils.isNewExpression(node.parent)) { return undefined; } if (tsutils.isTypeReferenceNode(node.parent)) { return undefined; } if (util_1.isConstDeclaration(declaration)) { return undefined; } if (tsutils.isImportSpecifier(declaration)) { return undefined; } if (tsutils.isNamespaceImport(declaration)) { return undefined; } return node; } } function getPropertyAccessExpressionRoot(node) { let { expression } = node; while (tsutils.isPropertyAccessExpression(expression)) { expression = expression.expression; } return util_1.isThis(expression) || tsutils.isIdentifier(expression) ? expression : undefined; } function isWithinClosure(declaration, callback) { return declaration.pos >= callback.pos && declaration.pos < callback.end; } function isPropertyAccessExpressionLeaf(node) { const { parent } = node; if (!tsutils.isPropertyAccessExpression(parent)) { return false; } if (node !== parent.name) { return false; } return !tsutils.isPropertyAccessExpression(parent.parent); }