@locker/ast-lib-maker
Version:
Lightning Web Security AST library maker utilities
227 lines (225 loc) • 7.12 kB
JavaScript
/*!
* Copyright (C) 2020 salesforce.com, inc.
*/
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
var shared = require('@locker/shared');
const ASTERISK_CHARACTER = '*';
const DOCUMENT_ALIASES = [`${ASTERISK_CHARACTER}.ownerDocument`, 'document'];
const WINDOW_ALIASES = ['document.defaultView', 'frames', 'globalThis', 'parent', 'self', 'top', 'window'];
function dehydratedPatternStartsWith(dehydratedPattern, searchString) {
return dehydratedPattern.startsWith(`${searchString}.`);
}
function dehydratedPatternStartsWithByAliases(dehydratedPattern, aliases) {
for (let i = 0, {
length
} = aliases; i < length; i += 1) {
if (dehydratedPatternStartsWith(dehydratedPattern, aliases[i])) {
return true;
}
}
return false;
}
function dehydratedPatternTail(dehydratedPattern) {
return dehydratedPattern.slice(dehydratedPattern.indexOf('.') + 1);
}
function dehydratePattern(pattern) {
return pattern.join('.');
}
function expandPattern(dehydratedPattern) {
const expandedPatterns = [];
// Exit early for patterns starting with '*.'.
if (dehydratedPatternStartsWith(dehydratedPattern, ASTERISK_CHARACTER)) {
return expandedPatterns;
}
const dehydratedPatterns = [dehydratedPattern];
const push = otherDehydratedPattern => {
if (!dehydratedPatterns.includes(otherDehydratedPattern)) {
dehydratedPatterns.push(otherDehydratedPattern);
expandedPatterns.push(rehydratePattern(otherDehydratedPattern));
}
};
const pushAliases = (aliases, pathTail) => {
for (let i = 0, {
length
} = aliases; i < length; i += 1) {
push(`${aliases[i]}.${pathTail}`);
}
};
const topLevelPath = isWindowStringPattern(dehydratedPattern) ? dehydratedPatternTail(dehydratedPattern) : dehydratedPattern;
// Expand patterns with a top-level identifier.
push(topLevelPath);
// Expand patterns with other `window` aliases.
pushAliases(WINDOW_ALIASES, topLevelPath);
// Expand patterns with other `document` aliases.
if (isDocumentStringPattern(topLevelPath)) {
pushAliases(DOCUMENT_ALIASES, dehydratedPatternTail(topLevelPath));
}
return expandedPatterns;
}
function isDocumentStringPattern(dehydratedPattern) {
return dehydratedPatternStartsWithByAliases(dehydratedPattern, DOCUMENT_ALIASES);
}
function isWindowStringPattern(dehydratedPattern) {
return dehydratedPatternStartsWithByAliases(dehydratedPattern, WINDOW_ALIASES);
}
function rehydratePattern(dehydratedPattern) {
return dehydratedPattern.split('.');
}
const baseLib = {
/**
* String patterns are converted to arrays and expanded for document and
* window aliases. For example:
* ```js
* ['window.top']
* ```
* expands to:
* ```js
* [
* ['window', 'top'],
* ['top'],
* ['document', 'defaultView', 'top'],
* ['frames', 'top'],
* ['globalThis', 'top'],
* ['self', 'top']
* ]
* ```
*/
expandPatterns(dehydratedPatterns) {
const expandedPatterns = [];
for (let i = 0, {
length
} = dehydratedPatterns; i < length; i += 1) {
expandedPatterns.push(rehydratePattern(dehydratedPatterns[i]));
}
for (let i = 0, {
length
} = dehydratedPatterns; i < length; i += 1) {
const patterns = expandPattern(dehydratedPatterns[i]);
// eslint-disable-next-line @typescript-eslint/naming-convention
for (let j = 0, {
length: length_j
} = patterns; j < length_j; j += 1) {
expandedPatterns.push(patterns[j]);
}
}
return expandedPatterns;
},
getIdentifierName(node) {
return node.name;
},
getParent(node) {
return node.parent;
},
getType(node) {
return node.type;
},
isMatchableNode(node) {
if (node) {
const type = this.getType(node);
return type === 'Identifier' || type === 'ThisExpression';
}
return false;
},
isNodeByAsteriskOrName(node, name) {
if (this.isMatchableNode(node)) {
return name === ASTERISK_CHARACTER || name === this.getIdentifierName(node) || this.getType(node) === 'ThisExpression';
}
return false;
},
isNonComputedMemberExpressionNode(node) {
if (node) {
return this.getType(node) === 'MemberExpression' && !node.computed;
}
return false;
},
match(identifier, patterns) {
expandedLoop: for (let i = 0, {
length
} = patterns; i < length; i += 1) {
const pattern = patterns[i];
// Skip fast for mismatched identifiers.
if (!this.isNodeByAsteriskOrName(identifier, pattern[0])) {
continue;
}
// Match and exit early for non-member identifier matches.
if (pattern.length === 1) {
return {
identifier,
node: identifier,
pattern: dehydratePattern(pattern)
};
}
let currentNode = identifier;
// eslint-disable-next-line @typescript-eslint/naming-convention
for (let j = 0, {
length: length_j
} = pattern; j < length_j; j += 1) {
currentNode = this.getParent(currentNode);
// Skip for unexpected nodes.
if (!this.isNonComputedMemberExpressionNode(currentNode)) {
continue expandedLoop;
}
const {
object,
property
} = currentNode;
let matchedByPreviousAsterisk = false;
if (this.isMatchableNode(object)) {
// Skip for mismatched object identifiers.
if (!this.isNodeByAsteriskOrName(object, pattern[j])) {
matchedByPreviousAsterisk = pattern[j - 1] === ASTERISK_CHARACTER;
// istanbul ignore else: currently unreachable via tests
if (!matchedByPreviousAsterisk) {
continue expandedLoop;
}
}
// istanbul ignore else: currently unreachable via tests
if (!matchedByPreviousAsterisk) {
j += 1;
}
}
// Skip nodes that aren't part of a property chain.
else if (!this.isNonComputedMemberExpressionNode(object)) {
continue expandedLoop;
}
// Skip for mismatched property identifiers.
if (!this.isNodeByAsteriskOrName(property, pattern[j])) {
matchedByPreviousAsterisk = pattern[j - 1] === ASTERISK_CHARACTER;
if (!matchedByPreviousAsterisk) {
continue expandedLoop;
}
}
if (matchedByPreviousAsterisk) {
j -= 1;
}
}
// If we've made it this far it's a match!
return {
identifier,
node: currentNode,
pattern: dehydratePattern(pattern)
};
}
return undefined;
},
matchAll(identifiers, patterns) {
const matches = [];
for (let i = 0, {
length
} = identifiers; i < length; i += 1) {
const matchData = this.match(identifiers[i], patterns);
if (matchData) {
matches.push(matchData);
}
}
return matches;
}
};
function createLib(overrides) {
return shared.ObjectAssign({}, baseLib, overrides);
}
exports.createLib = createLib;
/*! version: 0.25.3 */