Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@locker/eslint-rule-maker

Package Overview
Dependencies
Maintainers
6
Versions
224
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@locker/eslint-rule-maker - npm Package Compare versions

Comparing version 0.11.7 to 0.11.9

dist/index.js

350

dist/index.cjs.js
/**
* Copyright (C) 2019 salesforce.com, inc.
* Copyright (C) 2020 salesforce.com, inc.
*/

@@ -8,188 +8,206 @@ 'use strict';

var shared = require('@locker/shared');
var astLibMaker = require('@locker/ast-lib-maker');
const NO_FIX_OVERRIDE = {
fix: null,
};
const defaultConfig = {
create: undefined,
meta: {
type: 'suggestion',
docs: {
category: 'Locker',
},
schema: [],
fixable: undefined,
type: 'problem',
},
rule: {
fix: undefined,
message: '',
message: undefined,
onMatch: undefined,
search: [],
},
};
const { hasOwnProperty } = Object.prototype;
const { isArray } = Array;
const windowAliases = ['frames', 'globalThis', 'self', 'window'];
function cloneConfig(config) {
return JSON.parse(JSON.stringify(config));
const configClone = JSON.parse(JSON.stringify(config));
if (shared.isFunction(config.create)) {
configClone.create = config.create;
}
if (shared.isObject(config.rule)) {
const rule = config.rule;
if (shared.isFunction(rule.fix)) {
configClone.rule.fix = rule.fix;
}
if (shared.isFunction(rule.message)) {
configClone.rule.message = rule.message;
}
if (shared.isFunction(rule.onMatch)) {
configClone.rule.onMatch = rule.onMatch;
}
}
return configClone;
}
function defaults(object, source) {
for (const name in source) {
if (has(source, name) && (object[name] === undefined || !has(object, name))) {
object[name] = source[name];
function getGlobalScopeByContext(context) {
return context.getSourceCode().scopeManager.globalScope;
}
function getGlobalIdentifiersByContext(context) {
const scope = getGlobalScopeByContext(context);
const identifiers = [];
// eslint-disable-next-line no-restricted-syntax
for (const variable of scope.variables) {
// ESLint global identifiers have the 'writeable' key.
if (typeof variable.writeable === 'boolean') {
// eslint-disable-next-line no-restricted-syntax
for (const { identifier } of variable.references) {
identifiers.push(identifier);
}
}
}
return object;
// eslint-disable-next-line no-restricted-syntax
for (const { identifier } of scope.through) {
identifiers.push(identifier);
}
return identifiers;
}
function has(object, name) {
return object != null && hasOwnProperty.call(object, name);
function getScopeIdentifiersByContext(context) {
const scope = context.getScope();
const identifiers = [];
// eslint-disable-next-line no-restricted-syntax
for (const variable of scope.variables) {
// eslint-disable-next-line no-restricted-syntax
for (const { identifier } of variable.references) {
identifiers.push(identifier);
}
}
return identifiers;
}
function isWindowSearch(searchPath) {
return windowAliases.some(alias => searchPath.startsWith(`${alias}.`));
function inNodeSet(set, node) {
let currentNode = node;
while (currentNode !== null) {
if (set.has(currentNode)) {
return true;
}
currentNode = currentNode.parent;
}
return false;
}
/**
* While the `search` property is defined as an array of object property path
* arrays, for ease of use popular convention is to provide an array of object
* property path strings instead:
* ```js
* search: [
* 'document.defaultView.top',
* 'window.top'
* ]
* ```
* The string paths are expanded, for window aliases, and converted to arrays:
* ```js
* search: [
* ['document', 'defaultView', 'top'],
* ['window', 'top'],
* ['top'],
* ['frames', 'top'],
* ['globalThis', 'top'],
* ['self', 'top']
* ]
* ```
*/
function normalizeSearch(search) {
return (search
.map((searchPath) => {
// Dehydrate search paths to strings.
searchPath = isArray(searchPath) ? searchPath.join('.') : String(searchPath);
// Remove whitespace.
return searchPath.replace(/\s/g, '');
})
.reduce((expanded, searchPath, _index, dehydrated) => {
// Skip empty search paths.
if (searchPath !== '') {
expanded.push(searchPath);
const topLevelPath = isWindowSearch(searchPath)
? searchPath.slice(searchPath.indexOf('.') + 1)
: searchPath;
// Expand search paths to a top-level identifier.
if (searchPath !== topLevelPath && !dehydrated.includes(topLevelPath)) {
expanded.push(topLevelPath);
const ast = astLibMaker.createLib();
function matchAsNonReadableNonWritable({ node }) {
return ast.isNodeOfType(node, 'MemberExpression') && NO_FIX_OVERRIDE;
}
function matchAsNonWritable({ node }) {
const parent = ast.getParentNode(node);
return (ast.isNodeOfType(parent, 'AssignmentExpression') && parent.left === node && NO_FIX_OVERRIDE);
}
function matchAsNullishAndNonWritable(data) {
const { node } = data;
const parent = ast.getParentNode(node);
// If `parent` is a MemberExpression then its AST represents a child property
// access. For example, with a `pattern` of `window.top` the matched `node`
// represents `window.top` and `parent` represents `window.top.pageXOffset`
// which is a lint error since `window.top` is treated as nullish.
return ast.isNodeOfType(parent, 'MemberExpression') || matchAsNonWritable(data);
}
const matchers = {
matchAsNonReadableNonWritable,
matchAsNonWritable,
matchAsNullishAndNonWritable,
};
function createRule(config) {
const configClone = cloneConfig(config);
const defaultConfigClone = cloneConfig(defaultConfig);
defaultConfigClone.create = function create(context) {
const checked = new Set();
const reported = new Set();
function report(data) {
const matchedNode = data.node;
if (inNodeSet(reported, matchedNode)) {
return;
}
// Expand search paths to other `window` aliases.
for (const alias of windowAliases) {
const searchAliasPath = `${alias}.${topLevelPath}`;
if (searchPath !== searchAliasPath &&
!dehydrated.includes(searchAliasPath)) {
expanded.push(searchAliasPath);
reported.add(matchedNode);
const matchedData = {
context,
identifier: data.identifier,
node: matchedNode,
pattern: data.pattern,
};
const { onMatch } = configClone.rule;
const matcherData = !shared.isFunction(onMatch) || onMatch.call(config.rule, matchedData);
if (!matcherData) {
return;
}
let { fix, message } = configClone.rule;
if (shared.isObject(matcherData)) {
if (shared.ObjectHasOwnProperty(matcherData, 'fix')) {
fix = matcherData.fix;
}
if (shared.ObjectHasOwnProperty(matcherData, 'message')) {
message = matcherData.message;
}
}
if (typeof fix === 'string') {
const replacementCode = fix;
fix = (fixer) => [
fixer.insertTextAfter(matchedNode, replacementCode),
fixer.remove(matchedNode),
];
}
else if (shared.isFunction(fix)) {
const oldFix = fix;
fix = (fixer) => oldFix.call(config.rule, fixer, matchedData);
}
else {
fix = undefined;
}
if (shared.isFunction(message)) {
message = message.call(config.rule, matchedData);
}
context.report({
fix: fix,
node: matchedNode,
message: message,
});
}
return expanded;
}, [])
// Rehydrate search paths to arrays.
.map(searchPath => searchPath.split('.')));
}
function create(config) {
const defaultConfigClone = cloneConfig(defaultConfig);
config.meta = defaults(config.meta, defaultConfigClone.meta);
config.rule = defaults(config.rule, defaultConfigClone.rule);
const { meta, rule } = config;
meta.docs = defaults(config.meta.docs, defaultConfigClone.meta.docs);
rule.search = normalizeSearch(rule.search);
if (meta.fixable === undefined && rule.fix !== undefined) {
meta.fixable = 'code';
return {
MemberExpression(node) {
if (inNodeSet(checked, node)) {
return;
}
checked.add(node);
const patternsWithAsterisks = configClone.rule.search.filter((pattern) => pattern[0] === '*');
const matches = ast.matchAll(getScopeIdentifiersByContext(context), patternsWithAsterisks);
// eslint-disable-next-line no-restricted-syntax
for (const matchData of matches) {
report(matchData);
}
},
'Program:exit': function ProgramExit() {
const matches = ast.matchAll(getGlobalIdentifiersByContext(context), configClone.rule.search);
// eslint-disable-next-line no-restricted-syntax
for (const matchData of matches) {
report(matchData);
}
},
};
};
// Populate first level default properties.
shared.defaults(configClone, defaultConfigClone);
// Populate second level default properties.
configClone.meta = shared.defaults(configClone.meta, defaultConfigClone.meta);
configClone.rule = shared.defaults(configClone.rule, defaultConfigClone.rule);
// Populate third level default properties.
configClone.rule.search = ast.expandPatterns(configClone.rule.search);
if (configClone.meta.fixable === undefined && configClone.rule.fix !== undefined) {
configClone.meta.fixable = 'code';
}
return {
meta,
create(context) {
return {
'Program:exit': function () {
let nodeForRef;
// The top level scope is the 'global' scope. Depending on
// the 'env' property in the eslint config, references might
// not be resolved at the global scope. The most reliable way
// to get all unresolved references from the module is to get
// them from the 'module' scope.
const moduleScope = context
.getScope()
.childScopes.find(({ type }) => type === 'module');
const ref = moduleScope.through.find(({ identifier }) => {
searchLoop: for (const searchPath of rule.search) {
// Skip fast for mismatched identifiers.
if (identifier.name !== searchPath[0]) {
continue;
}
// Exit early for non-member identifier matches.
if (searchPath.length === 1) {
nodeForRef = identifier;
return true;
}
// Skip fast for the unexpected.
const { parent } = identifier;
if (parent.type !== 'MemberExpression') {
continue;
}
let currentNode = parent;
for (let i = 0; i < searchPath.length; i += 1) {
nodeForRef = undefined;
// Skip for the unexpected.
if (currentNode === undefined ||
currentNode.type !== 'MemberExpression') {
continue searchLoop;
}
const { object, property } = currentNode;
if (object.type === 'Identifier') {
// Skip for mismatched object identifiers.
if (object.name !== searchPath[i]) {
continue searchLoop;
}
i += 1;
}
// Skip nodes that aren't part of a property chain.
else if (object.type !== 'MemberExpression') {
continue searchLoop;
}
// Skip for mismatched property identifiers.
if (property.type !== 'Identifier' ||
property.name !== searchPath[i]) {
continue searchLoop;
}
nodeForRef = currentNode;
currentNode = currentNode.parent;
}
// If we've made it this far it's a match!
return true;
}
return false;
});
if (ref) {
let { fix } = rule;
if (typeof fix === 'string') {
fix = (fixer) => [
fixer.insertTextAfter(nodeForRef, rule.fix),
fixer.remove(nodeForRef),
];
}
else if (typeof fix === 'function') {
fix = (fixer) => rule.fix.call(undefined, fixer, nodeForRef);
}
context.report({
fix,
node: nodeForRef,
message: rule.message,
});
}
},
};
},
};
if (shared.isObject(config.rule) && !shared.isFunction(configClone.rule.onMatch)) {
configClone.rule.onMatch = matchers.matchAsNullishAndNonWritable;
}
// Remove 'rule' from `exportedConfig` so it aligns with the expected
// ESRule.RuleModule interface.
const exportedConfig = cloneConfig(configClone);
delete exportedConfig.rule;
return exportedConfig;
}
exports.create = create;
/** version: 0.11.7 */
exports.ast = ast;
exports.createRule = createRule;
exports.matchers = matchers;
/** version: 0.11.9 */
{
"name": "@locker/eslint-rule-maker",
"version": "0.11.7",
"version": "0.11.9",
"license": "MIT",
"description": "Locker Next eslint rule maker utility package",
"description": "Locker Next eslint rule maker utility",
"keywords": [

@@ -17,3 +17,3 @@ "eslint",

"clean": "locker-trash dist/ types/",
"test": "jest"
"test": "yarn build && jest"
},

@@ -23,4 +23,10 @@ "publishConfig": {

},
"dependencies": {
"@locker/ast-lib-maker": "0.11.9",
"@locker/shared": "0.11.9"
},
"devDependencies": {
"@types/eslint": "6.8.0",
"@types/eslint": "7.2.2",
"@types/estree": "0.0.45",
"eslint": "7.2.0",
"rollup-plugin-typescript": "1.0.1",

@@ -30,5 +36,5 @@ "typescript": "3.8.3"

"files": [
"lib"
"dist"
],
"gitHead": "d95b59d9623ee44939cd6d3f2b33b487ea308bdc"
"gitHead": "7fd681f08f87206dfb944c258dac4eb12a98a6eb"
}
# @locker/eslint-rule-maker
The `@locker/eslint-rule-maker` package is used to help make linting rules
for [`@locker`](https://github.com/salesforce/locker).
The `@locker/eslint-rule-maker` package is used to make linting rules for
[`@locker`](https://github.com/salesforce/locker).
## Usage
Use the `create()` utility when defining an eslint rule.
Use `createRule(config)` when defining an eslint rule.
<!-- eslint-disable import/no-extraneous-dependencies -->
```js
const { create } = require('@locker/eslint-rule-maker');
const { createRule } = require('@locker/eslint-rule-maker');
module.exports = create({
// Add a meta object according to
module.exports = createRule({
// Add an optional meta object according to
// https://eslint.org/docs/developer-guide/working-with-rules

@@ -19,4 +19,3 @@ //

// `meta.fixable`: `'code'` (when specifying `rule.fix`)
// `meta.type`: `'suggestion'`
// `meta.docs.category`: `'Locker'`
// `meta.type`: `'problem'`
rule: {

@@ -28,5 +27,5 @@ // The message provided to `context.report()`.

// An array of object property paths to search for.
search: ['document.defaultView.top', 'window.top'],
search: ['window.top'],
},
});
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc