eslint-plugin-sonarjs
Version:
76 lines (75 loc) • 3.34 kB
JavaScript
;
/*
* SonarQube JavaScript Plugin
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.unwrapChainExpression = unwrapChainExpression;
exports.collectCallChain = collectCallChain;
exports.getRootCall = getRootCall;
const ast_js_1 = require("./ast.js");
function unwrapChainExpression(node) {
return node.type === 'ChainExpression' ? node.expression : node;
}
/**
* Collects the chain of member names hung off `call`, furthest from the root
* call first, e.g. for `expect(x).not.resolves.toBe(y)` called with the `toBe(y)`
* node, returns
* `{ segments: [{name: 'toBe', ...}, {name: 'resolves', ...}, {name: 'not', ...}], complete: true }`.
* `complete` is `false` when a computed member or non-identifier property was hit
* before reaching the root call, in which case `segments` only holds the
* well-formed prefix collected so far — callers that need the full chain to be
* a recognised, closed set of names (rather than just searching the collected
* prefix for one specific name) should treat an incomplete chain as unresolved.
*/
function collectCallChain(call) {
const segments = [];
let current = call;
while (current.type === 'CallExpression' || current.type === 'MemberExpression') {
if (current.type === 'CallExpression') {
current = unwrapChainExpression(current.callee);
continue;
}
if (current.computed || !(0, ast_js_1.isIdentifier)(current.property)) {
return { segments, complete: false };
}
segments.push({ name: current.property.name, node: current.property });
current = unwrapChainExpression(current.object);
}
return { segments, complete: true };
}
/**
* Walks down from `call` to the root call of its member/call chain, e.g. for
* `expect(x).not.resolves.toBe(y)` called with the `toBe(y)` node, returns the
* `expect(x)` call. Tolerates computed members and non-identifier properties
* anywhere in the chain, since finding the root call doesn't depend on the
* chain's named segments being fully resolvable.
*/
function getRootCall(call) {
let current = call;
while (current.type === 'CallExpression' || current.type === 'MemberExpression') {
if (current.type === 'CallExpression') {
const callee = unwrapChainExpression(current.callee);
if (callee.type !== 'MemberExpression' ||
(callee.object.type !== 'CallExpression' && callee.object.type !== 'MemberExpression')) {
return current;
}
current = callee;
continue;
}
current = unwrapChainExpression(current.object);
}
return null;
}