@hao360/eslint-plugin-cube
Advanced tools
| "use strict"; | ||
| const { getStaticValue } = require("eslint-utils"); | ||
| const { | ||
| isSpecificMemberAccess, | ||
| getVariableByName, | ||
| isSpecificId, | ||
| getStaticPropertyName, | ||
| } = require("../utils"); | ||
| const callMethods = new Set(["apply", "bind", "call"]); | ||
| module.exports = { | ||
| meta: { | ||
| docs: { | ||
| description: "", | ||
| recommended: false | ||
| } | ||
| }, | ||
| create: function(context) { | ||
| const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); | ||
| const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; | ||
| function isEvaluatedString(node) { | ||
| if ( | ||
| (node.type === "Literal" && typeof node.value === "string") || | ||
| node.type === "TemplateLiteral" | ||
| ) { | ||
| return true; | ||
| } | ||
| if (node.type === "BinaryExpression" && node.operator === "+") { | ||
| return isEvaluatedString(node.left) || isEvaluatedString(node.right); | ||
| } | ||
| return false; | ||
| } | ||
| function reportImpliedEvalCallExpression(node) { | ||
| const [firstArgument] = node.arguments; | ||
| if (firstArgument) { | ||
| const staticValue = getStaticValue(firstArgument, context.getScope()); | ||
| const isStaticString = staticValue && typeof staticValue.value === "string"; | ||
| const isString = isStaticString || isEvaluatedString(firstArgument); | ||
| if (isString) { | ||
| context.report({ | ||
| node, | ||
| message: "禁止动态执行js" | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| function reportImpliedEvalViaGlobal(globalVar) { | ||
| const { references, name } = globalVar; | ||
| references.forEach(ref => { | ||
| const identifier = ref.identifier; | ||
| let node = identifier.parent; | ||
| while (isSpecificMemberAccess(node, null, name)) { | ||
| node = node.parent; | ||
| } | ||
| if (isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) { | ||
| const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node; | ||
| const parent = calleeNode.parent; | ||
| if (parent.type === "CallExpression" && parent.callee === calleeNode) { | ||
| reportImpliedEvalCallExpression(parent); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| return { | ||
| CallExpression(node) { | ||
| if (isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) { | ||
| reportImpliedEvalCallExpression(node); | ||
| } | ||
| }, | ||
| "Program:exit"() { | ||
| const globalScope = context.getScope(); | ||
| const variable = globalScope.set.get("Function"); | ||
| GLOBAL_CANDIDATES | ||
| .map(candidate => getVariableByName(globalScope, candidate)) | ||
| .filter(globalVar => !!globalVar && globalVar.defs.length === 0) | ||
| .forEach(reportImpliedEvalViaGlobal); | ||
| if (variable && variable.defs.length === 0) { | ||
| variable.references.forEach(ref => { | ||
| const node = ref.identifier; | ||
| const { parent } = node; | ||
| let evalNode; | ||
| if (parent) { | ||
| if (node === parent.callee && ( | ||
| parent.type === "NewExpression" || | ||
| parent.type === "CallExpression" | ||
| )) { | ||
| evalNode = parent; | ||
| } else if ( | ||
| parent.type === "MemberExpression" && | ||
| node === parent.object && | ||
| callMethods.has(getStaticPropertyName(parent)) | ||
| ) { | ||
| const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent; | ||
| if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) { | ||
| evalNode = maybeCallee.parent; | ||
| } | ||
| } | ||
| } | ||
| if (evalNode) { | ||
| context.report({ | ||
| node: evalNode, | ||
| message: "禁止动态执行js" | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }; |
| "use strict"; | ||
| const { | ||
| isSpecificMemberAccess, | ||
| getVariableByName, | ||
| isCallee, | ||
| isSpecificId, | ||
| isDefaultThisBinding, | ||
| } = require("../utils"); | ||
| const candidatesOfGlobalObject = Object.freeze([ | ||
| "global", | ||
| "window", | ||
| "globalThis" | ||
| ]); | ||
| function isMember(node, name) { | ||
| return isSpecificMemberAccess(node, null, name); | ||
| } | ||
| module.exports = { | ||
| meta: { | ||
| docs: { | ||
| description: "", | ||
| recommended: false | ||
| } | ||
| }, | ||
| create: function(context) { | ||
| const sourceCode = context.getSourceCode(); | ||
| let funcInfo = null; | ||
| function enterVarScope(node) { | ||
| const strict = context.getScope().isStrict; | ||
| funcInfo = { | ||
| upper: funcInfo, | ||
| node, | ||
| strict, | ||
| defaultThis: false, | ||
| initialized: strict | ||
| }; | ||
| } | ||
| function exitVarScope() { | ||
| funcInfo = funcInfo.upper; | ||
| } | ||
| function report(node) { | ||
| const parent = node.parent; | ||
| const locationNode = node.type === "MemberExpression" | ||
| ? node.property | ||
| : node; | ||
| const reportNode = parent.type === "CallExpression" && parent.callee === node | ||
| ? parent | ||
| : node; | ||
| context.report({ | ||
| node: reportNode, | ||
| message: "禁止使用eval" | ||
| }); | ||
| } | ||
| function reportAccessingEvalViaGlobalObject(globalScope) { | ||
| for (let i = 0; i < candidatesOfGlobalObject.length; ++i) { | ||
| const name = candidatesOfGlobalObject[i]; | ||
| const variable = getVariableByName(globalScope, name); | ||
| if (!variable) { | ||
| continue; | ||
| } | ||
| const references = variable.references; | ||
| for (let j = 0; j < references.length; ++j) { | ||
| const identifier = references[j].identifier; | ||
| let node = identifier.parent; | ||
| while (isMember(node, name)) { | ||
| node = node.parent; | ||
| } | ||
| if (isMember(node, "eval")) { | ||
| report(node); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function reportAccessingEval(globalScope) { | ||
| const variable = getVariableByName(globalScope, "eval"); | ||
| if (!variable) { | ||
| return; | ||
| } | ||
| const references = variable.references; | ||
| for (let i = 0; i < references.length; ++i) { | ||
| const reference = references[i]; | ||
| const id = reference.identifier; | ||
| if (id.name === "eval" && !isCallee(id)) { | ||
| report(id); | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| "CallExpression:exit"(node) { | ||
| const callee = node.callee; | ||
| if (isSpecificId(callee, "eval")) { | ||
| report(callee); | ||
| } | ||
| }, | ||
| Program(node) { | ||
| const scope = context.getScope(), | ||
| features = context.parserOptions.ecmaFeatures || {}, | ||
| strict = | ||
| scope.isStrict || | ||
| node.sourceType === "module" || | ||
| (features.globalReturn && scope.childScopes[0].isStrict); | ||
| funcInfo = { | ||
| upper: null, | ||
| node, | ||
| strict, | ||
| defaultThis: true, | ||
| initialized: true | ||
| }; | ||
| }, | ||
| "Program:exit"() { | ||
| const globalScope = context.getScope(); | ||
| exitVarScope(); | ||
| reportAccessingEval(globalScope); | ||
| reportAccessingEvalViaGlobalObject(globalScope); | ||
| }, | ||
| FunctionDeclaration: enterVarScope, | ||
| "FunctionDeclaration:exit": exitVarScope, | ||
| FunctionExpression: enterVarScope, | ||
| "FunctionExpression:exit": exitVarScope, | ||
| ArrowFunctionExpression: enterVarScope, | ||
| "ArrowFunctionExpression:exit": exitVarScope, | ||
| "PropertyDefinition > *.value": enterVarScope, | ||
| "PropertyDefinition > *.value:exit": exitVarScope, | ||
| StaticBlock: enterVarScope, | ||
| "StaticBlock:exit": exitVarScope, | ||
| ThisExpression(node) { | ||
| if (!isMember(node.parent, "eval")) { | ||
| return; | ||
| } | ||
| if (!funcInfo.initialized) { | ||
| funcInfo.initialized = true; | ||
| funcInfo.defaultThis = isDefaultThisBinding( | ||
| funcInfo.node, | ||
| sourceCode | ||
| ); | ||
| } | ||
| if (!funcInfo.strict && funcInfo.defaultThis) { | ||
| report(node.parent); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }; |
+1
-0
@@ -22,2 +22,3 @@ exports.windowGlobal = [ | ||
| "window", | ||
| "globalThis", | ||
| "document", | ||
@@ -24,0 +25,0 @@ "name", |
+4
-2
@@ -6,3 +6,4 @@ "use strict"; | ||
| const noCubeTags = require("./rules/no-cube-tags"); | ||
| const noCubeWindow = require("./rules/no-cube-window"); | ||
| const noCubeDynamic = require("./rules/no-cube-dynamic"); | ||
| const noCubeEval = require("./rules/no-cube-eval"); | ||
@@ -13,3 +14,4 @@ module.exports.rules = { | ||
| 'no-cube-tags': noCubeTags, | ||
| 'no-cube-window': noCubeWindow, | ||
| 'no-cube-eval': noCubeEval, | ||
| 'no-cube-dynamic': noCubeDynamic, | ||
| } | ||
@@ -16,0 +18,0 @@ module.exports.processors = { |
+216
-1
@@ -0,1 +1,8 @@ | ||
| const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u; | ||
| const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u; | ||
| const arrayOrTypedArrayPattern = /Array$/u; | ||
| const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u; | ||
| const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u; | ||
| const thisTagPattern = /^[\s*]*@this/mu; | ||
| function isNullLiteral(node) { | ||
@@ -68,2 +75,206 @@ return node.type === "Literal" && node.value === null && !node.regex && !node.bigint; | ||
| function isCallee(node) { | ||
| return node.parent.type === "CallExpression" && node.parent.callee === node; | ||
| } | ||
| function isSpecificId(node, name) { | ||
| return node.type === "Identifier" && checkText(node.name, name); | ||
| } | ||
| function checkText(actual, expected) { | ||
| return typeof expected === "string" | ||
| ? actual === expected | ||
| : expected.test(actual); | ||
| } | ||
| function skipChainExpression(node) { | ||
| return node && node.type === "ChainExpression" ? node.expression : node; | ||
| } | ||
| function isSpecificMemberAccess(node, objectName, propertyName) { | ||
| const checkNode = skipChainExpression(node); | ||
| if (checkNode.type !== "MemberExpression") { | ||
| return false; | ||
| } | ||
| if (objectName && !isSpecificId(checkNode.object, objectName)) { | ||
| return false; | ||
| } | ||
| if (propertyName) { | ||
| const actualPropertyName = getStaticPropertyName(checkNode); | ||
| if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| function startsWithUpperCase(s) { | ||
| return s[0] !== s[0].toLocaleLowerCase(); | ||
| } | ||
| function isES5Constructor(node) { | ||
| return (node.id && startsWithUpperCase(node.id.name)); | ||
| } | ||
| function hasJSDocThisTag(node, sourceCode) { | ||
| const jsdocComment = sourceCode.getJSDocComment(node); | ||
| if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { | ||
| return true; | ||
| } | ||
| return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value)); | ||
| } | ||
| function getUpperFunction(node) { | ||
| for (let currentNode = node; currentNode; currentNode = currentNode.parent) { | ||
| if (anyFunctionPattern.test(currentNode.type)) { | ||
| return currentNode; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function isNullOrUndefined(node) { | ||
| return ( | ||
| isNullLiteral(node) || | ||
| (node.type === "Identifier" && node.name === "undefined") || | ||
| (node.type === "UnaryExpression" && node.operator === "void") | ||
| ); | ||
| } | ||
| function isReflectApply(node) { | ||
| return isSpecificMemberAccess(node, "Reflect", "apply"); | ||
| } | ||
| function isArrayFromMethod(node) { | ||
| return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from"); | ||
| } | ||
| function isMethodWhichHasThisArg(node) { | ||
| return isSpecificMemberAccess(node, null, arrayMethodPattern); | ||
| } | ||
| function isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { | ||
| if (node.parent.type === "PropertyDefinition" && node.parent.value === node) { | ||
| return false; | ||
| } | ||
| if (node.type === "StaticBlock") { | ||
| return false; | ||
| } | ||
| if ( | ||
| (capIsConstructor && isES5Constructor(node)) || | ||
| hasJSDocThisTag(node, sourceCode) | ||
| ) { | ||
| return false; | ||
| } | ||
| const isAnonymous = node.id === null; | ||
| let currentNode = node; | ||
| while (currentNode) { | ||
| const parent = currentNode.parent; | ||
| switch (parent.type) { | ||
| case "LogicalExpression": | ||
| case "ConditionalExpression": | ||
| case "ChainExpression": | ||
| currentNode = parent; | ||
| break; | ||
| case "ReturnStatement": { | ||
| const func = getUpperFunction(parent); | ||
| if (func === null || !isCallee(func)) { | ||
| return true; | ||
| } | ||
| currentNode = func.parent; | ||
| break; | ||
| } | ||
| case "ArrowFunctionExpression": | ||
| if (currentNode !== parent.body || !isCallee(parent)) { | ||
| return true; | ||
| } | ||
| currentNode = parent.parent; | ||
| break; | ||
| case "Property": | ||
| case "PropertyDefinition": | ||
| case "MethodDefinition": | ||
| return parent.value !== currentNode; | ||
| case "AssignmentExpression": | ||
| case "AssignmentPattern": | ||
| if (parent.left.type === "MemberExpression") { | ||
| return false; | ||
| } | ||
| if ( | ||
| capIsConstructor && | ||
| isAnonymous && | ||
| parent.left.type === "Identifier" && | ||
| startsWithUpperCase(parent.left.name) | ||
| ) { | ||
| return false; | ||
| } | ||
| return true; | ||
| case "VariableDeclarator": | ||
| return !( | ||
| capIsConstructor && | ||
| isAnonymous && | ||
| parent.init === currentNode && | ||
| parent.id.type === "Identifier" && | ||
| startsWithUpperCase(parent.id.name) | ||
| ); | ||
| case "MemberExpression": | ||
| if ( | ||
| parent.object === currentNode && | ||
| isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern) | ||
| ) { | ||
| const maybeCalleeNode = parent.parent.type === "ChainExpression" | ||
| ? parent.parent | ||
| : parent; | ||
| return !( | ||
| isCallee(maybeCalleeNode) && | ||
| maybeCalleeNode.parent.arguments.length >= 1 && | ||
| !isNullOrUndefined(maybeCalleeNode.parent.arguments[0]) | ||
| ); | ||
| } | ||
| return true; | ||
| case "CallExpression": | ||
| if (isReflectApply(parent.callee)) { | ||
| return ( | ||
| parent.arguments.length !== 3 || | ||
| parent.arguments[0] !== currentNode || | ||
| isNullOrUndefined(parent.arguments[1]) | ||
| ); | ||
| } | ||
| if (isArrayFromMethod(parent.callee)) { | ||
| return ( | ||
| parent.arguments.length !== 3 || | ||
| parent.arguments[1] !== currentNode || | ||
| isNullOrUndefined(parent.arguments[2]) | ||
| ); | ||
| } | ||
| if (isMethodWhichHasThisArg(parent.callee)) { | ||
| return ( | ||
| parent.arguments.length !== 2 || | ||
| parent.arguments[0] !== currentNode || | ||
| isNullOrUndefined(parent.arguments[1]) | ||
| ); | ||
| } | ||
| return true; | ||
| default: | ||
| return true; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| module.exports = { | ||
@@ -73,3 +284,7 @@ getVariableByName, | ||
| getStaticStringValue, | ||
| isNullLiteral | ||
| isNullLiteral, | ||
| isCallee, | ||
| isSpecificId, | ||
| isSpecificMemberAccess, | ||
| isDefaultThisBinding, | ||
| } |
+1
-1
| { | ||
| "name": "@hao360/eslint-plugin-cube", | ||
| "version": "0.1.5", | ||
| "version": "0.2.0", | ||
| "description": "cube rule validation plugin", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
+3
-1
@@ -15,3 +15,5 @@ # eslint-plugin-cube | ||
| '@hao360/cube/no-cube-tags', | ||
| '@hao360/cube/no-cube-attributes' | ||
| '@hao360/cube/no-cube-attributes', | ||
| '@hao360/cube/no-cube-eval', | ||
| '@hao360/cube/no-cube-dynamic' | ||
| } | ||
@@ -18,0 +20,0 @@ } |
| "use strict"; | ||
| const { | ||
| getStaticPropertyName | ||
| } = require("../utils"); | ||
| const restrictedCalls = [ | ||
| 'window' | ||
| ] | ||
| module.exports = { | ||
| meta: { | ||
| docs: { | ||
| description: "", | ||
| recommended: false | ||
| } | ||
| }, | ||
| create: function(context) { | ||
| const globallyRestrictedObjects = new Map(); | ||
| restrictedCalls.forEach(objectName => { | ||
| globallyRestrictedObjects.set(objectName, { message: `禁止使用${objectName}` }); | ||
| }); | ||
| function checkPropertyAccess(node, objectName, propertyName) { | ||
| if (propertyName === null) { | ||
| return; | ||
| } | ||
| const matchedObjectProperty = globallyRestrictedObjects.get(objectName); | ||
| if (matchedObjectProperty) { | ||
| const message = matchedObjectProperty.message; | ||
| context.report({ | ||
| node, | ||
| message | ||
| }); | ||
| } | ||
| } | ||
| function checkDestructuringAssignment(node) { | ||
| if (node.right.type === "Identifier") { | ||
| const objectName = node.right.name; | ||
| if (node.left.type === "ObjectPattern") { | ||
| node.left.properties.forEach(property => { | ||
| checkPropertyAccess(node.left, objectName, getStaticPropertyName(property)); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| MemberExpression(node) { | ||
| checkPropertyAccess(node, node.object && node.object.name, getStaticPropertyName(node)); | ||
| }, | ||
| VariableDeclarator(node) { | ||
| if (node.init && node.init.type === "Identifier") { | ||
| const objectName = node.init.name; | ||
| if (node.id.type === "ObjectPattern") { | ||
| node.id.properties.forEach(property => { | ||
| checkPropertyAccess(node.id, objectName, getStaticPropertyName(property)); | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| AssignmentExpression: checkDestructuringAssignment, | ||
| AssignmentPattern: checkDestructuringAssignment | ||
| } | ||
| } | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
48839
35.54%10
11.11%1955
23.89%40
5.26%0
-100%