eslint-plugin-canonical
Version:
Canonical linting rules for ESLint.
165 lines (164 loc) • 6.22 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-prototype-builtins */
/**
* @author https://github.com/dustinspecker/eslint-plugin-no-use-extend-native/blob/master/src/no-use-extend-native.js
*/
const is_get_set_prop_1 = __importDefault(require("is-get-set-prop"));
const is_js_type_1 = __importDefault(require("is-js-type"));
const is_obj_prop_1 = __importDefault(require("is-obj-prop"));
const is_proto_prop_1 = __importDefault(require("is-proto-prop"));
const utilities_1 = require("../utilities");
const isProtoProperty = (jsType, propertyName) => {
if (jsType === 'Object' && propertyName === 'groupBy') {
return true;
}
return (0, is_proto_prop_1.default)(jsType, propertyName);
};
/**
* Return type of value of left or right
*
* @param {object} subject - left or right of node.object
* @returns {string} - type of o
*/
const getType = (subject) => {
const type = typeof subject.value;
if (subject.regex) {
return 'RegExp';
}
return type.charAt(0).toUpperCase() + type.slice(1);
};
/**
* Returns type of binary expression result
*
* @param {object} subject - node's object with a BinaryExpression type
* @returns {string} - type of value produced
*/
const binaryExpressionProduces = (subject) => {
const leftType = subject.left.type === 'BinaryExpression'
? binaryExpressionProduces(subject.left)
: getType(subject.left);
const rightType = subject.right.type === 'BinaryExpression'
? binaryExpressionProduces(subject.right)
: getType(subject.right);
const isRegExp = leftType === rightType && leftType === 'RegExp';
if (leftType === 'String' || rightType === 'String' || isRegExp) {
return 'String';
}
if (leftType === rightType) {
return leftType;
}
return 'Unknown';
};
/**
* Returns the JS type and property name
*
* @param {object} node - node to examine
* @returns {object} - jsType and propertyName
*/
const getJsTypeAndPropertyName = (node) => {
let jsType;
let propertyName;
switch (node.object.type) {
case 'NewExpression':
jsType = node.object.callee.name;
break;
case 'Literal':
jsType = getType(node.object);
break;
case 'BinaryExpression':
jsType = binaryExpressionProduces(node.object);
break;
case 'Identifier':
if (node.property.name === 'prototype' && node.parent.property) {
jsType = node.object.name;
propertyName = node.parent.property.name;
}
else {
jsType = node.object.name;
}
break;
default:
jsType = node.object.type.replace('Expression', '');
}
propertyName = propertyName || node.property.name || node.property.value;
return {
jsType,
propertyName,
};
};
const isUnkownGettSetterOrJsTypeExpressed = (jsType, propertyName, usageType) => {
const isExpression = usageType === 'ExpressionStatement' || usageType === 'MemberExpression';
return (isExpression &&
!(0, is_get_set_prop_1.default)(jsType, propertyName) &&
!isProtoProperty(jsType, propertyName) &&
!(0, is_obj_prop_1.default)(jsType, propertyName));
};
/**
* Determine if a jsType's usage of propertyName is valid
*
* @param {string} jsType - the JS type to validate
* @param {string} propertyName - the property name to validate usage of on jsType
* @param {string} usageType - how propertyName is being used
* @returns {boolean} - is the usage invalid?
*/
const isInvalid = (jsType, propertyName, usageType) => {
if (typeof propertyName !== 'string' ||
typeof jsType !== 'string' ||
!(0, is_js_type_1.default)(jsType)) {
return false;
}
const unknownGetterSetterOrjsTypeExpressed = isUnkownGettSetterOrJsTypeExpressed(jsType, propertyName, usageType);
const isFunctionCall = usageType === 'CallExpression';
const getterSetterCalledAsFunction = isFunctionCall && (0, is_get_set_prop_1.default)(jsType, propertyName);
const unknownjsTypeCalledAsFunction = isFunctionCall &&
!isProtoProperty(jsType, propertyName) &&
!(0, is_obj_prop_1.default)(jsType, propertyName);
return (unknownGetterSetterOrjsTypeExpressed ||
getterSetterCalledAsFunction ||
unknownjsTypeCalledAsFunction);
};
exports.default = (0, utilities_1.createRule)({
create(context) {
return {
MemberExpression(node) {
var _a, _b;
if (node.computed && node.property.type === 'Identifier') {
/**
* handles cases like {}[i][j]
* not enough information to identify type of variable in computed properties
* so ignore false positives by not performing any checks
*/
return;
}
const isArgumentToParent = ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.hasOwnProperty('arguments')) &&
'arguments' in node.parent &&
node.parent.arguments.includes(node);
const usageType = isArgumentToParent ? node.type : (_b = node.parent) === null || _b === void 0 ? void 0 : _b.type;
const { propertyName, jsType } = getJsTypeAndPropertyName(node);
if (isInvalid(jsType, propertyName, usageType) &&
isInvalid('Function', propertyName, usageType)) {
context.report({
messageId: 'noExtendNative',
node,
});
}
},
};
},
defaultOptions: [],
meta: {
docs: {
description: '',
},
messages: {
noExtendNative: 'Avoid using extended native objects',
},
schema: [],
type: 'problem',
},
name: 'no-use-extend-native',
});