diff options
author | Leko <leko.noor@gmail.com> | 2020-09-30 02:29:40 +0800 |
---|---|---|
committer | Leko <leko.noor@gmail.com> | 2020-11-07 18:28:14 +0800 |
commit | cef144421c5ff6e9677ecf0b7a607000b744aa13 (patch) | |
tree | d50d0463aa07df3764fc7452415194411c60e19f /tools | |
parent | 1d525f55bc9f090a167acc286529b9a424dd0d5a (diff) | |
download | ios-node-v8-cef144421c5ff6e9677ecf0b7a607000b744aa13.tar.gz ios-node-v8-cef144421c5ff6e9677ecf0b7a607000b744aa13.tar.bz2 ios-node-v8-cef144421c5ff6e9677ecf0b7a607000b744aa13.zip |
tools: add new ESLint rule: prefer-primordials
I added a new custom ESLint rule to fix these problems.
We have a lot of replaceable codes with primordials.
Accessing built-in objects is restricted by existing rule
(no-restricted-globals), but accessing property in the built-in objects
is not restricted right now. We manually review codes that can be
replaced by primordials, but there's a lot of code that actually needs
to be fixed. We have often made pull requests to replace the primordials
with.
Restrict accessing global built-in objects such as `Promise`.
Restrict calling static methods such as `Array.from` or `Symbol.for`.
Don't restrict prototype methods to prevent false-positive.
PR-URL: https://github.com/nodejs/node/pull/35448
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Daijiro Wachi <daijiro.wachi@gmail.com>
Reviewed-By: Ben Coe <bencoe@gmail.com>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/eslint-rules/prefer-primordials.js | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/tools/eslint-rules/prefer-primordials.js b/tools/eslint-rules/prefer-primordials.js new file mode 100644 index 0000000000..ffbb1e6e30 --- /dev/null +++ b/tools/eslint-rules/prefer-primordials.js @@ -0,0 +1,156 @@ +/** + * @fileoverview We shouldn't use global built-in object for security and + * performance reason. This linter rule reports replacable codes + * that can be replaced with primordials. + * @author Leko <leko.noor@gmail.com> + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +function toPrimordialsName(obj, prop) { + return obj + toUcFirst(prop); +} + +function toUcFirst(str) { + return str[0].toUpperCase() + str.slice(1); +} + +function isTarget(map, varName) { + return map.has(varName); +} + +function isIgnored(map, varName, propName) { + if (!map.has(varName) || !map.get(varName).has(propName)) { + return false; + } + return map.get(varName).get(propName).ignored; +} + +function getReportName({ name, parentName, into }) { + if (into) { + return toPrimordialsName(into, name); + } + if (parentName) { + return toPrimordialsName(parentName, name); + } + return name; +} + +/** + * Get identifier of object spread assignment + * + * code: 'const { ownKeys } = Reflect;' + * argument: 'ownKeys' + * return: 'Reflect' + */ +function getDestructuringAssignmentParent(scope, node) { + const declaration = scope.set.get(node.name); + if ( + !declaration || + !declaration.defs || + declaration.defs.length === 0 || + declaration.defs[0].type !== 'Variable' || + !declaration.defs[0].node.init + ) { + return null; + } + return declaration.defs[0].node.init.name; +} + +const identifierSelector = + '[type!=VariableDeclarator][type!=MemberExpression]>Identifier'; + +module.exports = { + meta: { + messages: { + error: 'Use `const { {{name}} } = primordials;` instead of the global.' + } + }, + create(context) { + const globalScope = context.getSourceCode().scopeManager.globalScope; + const nameMap = context.options.reduce((acc, option) => + acc.set( + option.name, + (option.ignore || []) + .concat(['prototype']) + .reduce((acc, name) => acc.set(name, { + ignored: true + }), new Map()) + ) + , new Map()); + const renameMap = context.options + .filter((option) => option.into) + .reduce((acc, option) => + acc.set(option.name, option.into) + , new Map()); + let reported; + + return { + Program() { + reported = new Map(); + }, + [identifierSelector](node) { + if (reported.has(node.range[0])) { + return; + } + const name = node.name; + const parentName = getDestructuringAssignmentParent( + context.getScope(), + node + ); + if (!isTarget(nameMap, name) && !isTarget(nameMap, parentName)) { + return; + } + + const defs = (globalScope.set.get(name) || {}).defs || null; + if (parentName && isTarget(nameMap, parentName)) { + if (!defs || defs[0].name.name !== 'primordials') { + reported.set(node.range[0], true); + const into = renameMap.get(name); + context.report({ + node, + messageId: 'error', + data: { + name: getReportName({ into, parentName, name }) + } + }); + } + return; + } + if (defs.length === 0 || defs[0].node.init.name !== 'primordials') { + reported.set(node.range[0], true); + const into = renameMap.get(name); + context.report({ + node, + messageId: 'error', + data: { + name: getReportName({ into, parentName, name }) + } + }); + } + }, + MemberExpression(node) { + const obj = node.object.name; + const prop = node.property.name; + if (!prop || !isTarget(nameMap, obj) || isIgnored(nameMap, obj, prop)) { + return; + } + + const variables = + context.getSourceCode().scopeManager.getDeclaredVariables(node); + if (variables.length === 0) { + context.report({ + node, + messageId: 'error', + data: { + name: toPrimordialsName(obj, prop), + } + }); + } + } + }; + } +}; |