prettierx
Version:
prettierX - a less opinionated fork of the Prettier code formatter
198 lines (168 loc) • 5.27 kB
JavaScript
"use strict";
const getLast = require("../utils/get-last");
function getNodeHelper(path, count) {
const stackIndex = getNodeStackIndexHelper(path.stack, count);
return stackIndex === -1 ? null : path.stack[stackIndex];
}
function getNodeStackIndexHelper(stack, count) {
for (let i = stack.length - 1; i >= 0; i -= 2) {
const value = stack[i];
if (value && !Array.isArray(value) && --count < 0) {
return i;
}
}
return -1;
}
class AstPath {
constructor(value) {
this.stack = [value];
}
// The name of the current property is always the penultimate element of
// this.stack, and always a String.
getName() {
const { stack } = this;
const { length } = stack;
if (length > 1) {
return stack[length - 2];
}
// Since the name is always a string, null is a safe sentinel value to
// return if we do not know the name of the (root) value.
/* istanbul ignore next */
return null;
}
// The value of the current property is always the final element of
// this.stack.
getValue() {
return getLast(this.stack);
}
getNode(count = 0) {
return getNodeHelper(this, count);
}
getParentNode(count = 0) {
return getNodeHelper(this, count + 1);
}
// Temporarily push properties named by string arguments given after the
// callback function onto this.stack, then call the callback with a
// reference to this (modified) AstPath object. Note that the stack will
// be restored to its original state after the callback is finished, so it
// is probably a mistake to retain a reference to the path.
call(callback, ...names) {
const { stack } = this;
const { length } = stack;
let value = getLast(stack);
for (const name of names) {
value = value[name];
stack.push(name, value);
}
const result = callback(this);
stack.length = length;
return result;
}
callParent(callback, count = 0) {
const stackIndex = getNodeStackIndexHelper(this.stack, count + 1);
const parentValues = this.stack.splice(stackIndex + 1);
const result = callback(this);
this.stack.push(...parentValues);
return result;
}
// Similar to AstPath.prototype.call, except that the value obtained by
// accessing this.getValue()[name1][name2]... should be array. The
// callback will be called with a reference to this path object for each
// element of the array.
each(callback, ...names) {
const { stack } = this;
const { length } = stack;
let value = getLast(stack);
for (const name of names) {
value = value[name];
stack.push(name, value);
}
for (let i = 0; i < value.length; ++i) {
stack.push(i, value[i]);
callback(this, i, value);
stack.length -= 2;
}
stack.length = length;
}
// Similar to AstPath.prototype.each, except that the results of the
// callback function invocations are stored in an array and returned at
// the end of the iteration.
map(callback, ...names) {
const result = [];
this.each((path, index, value) => {
result[index] = callback(path, index, value);
}, ...names);
return result;
}
/**
* @param {() => void} callback
* @internal Unstable API. Don't use in plugins for now.
*/
try(callback) {
const { stack } = this;
const stackBackup = [...stack];
try {
return callback();
} finally {
stack.length = 0;
stack.push(...stackBackup);
}
}
/**
* @param {...(
* | ((node: any, name: string | null, number: number | null) => boolean)
* | undefined
* )} predicates
*/
match(...predicates) {
let stackPointer = this.stack.length - 1;
let name = null;
let node = this.stack[stackPointer--];
for (const predicate of predicates) {
/* istanbul ignore next */
if (node === undefined) {
return false;
}
// skip index/array
let number = null;
if (typeof name === "number") {
number = name;
name = this.stack[stackPointer--];
node = this.stack[stackPointer--];
}
if (predicate && !predicate(node, name, number)) {
return false;
}
name = this.stack[stackPointer--];
node = this.stack[stackPointer--];
}
return true;
}
/**
* Traverses the ancestors of the current node heading toward the tree root
* until it finds a node that matches the provided predicate function. Will
* return the first matching ancestor. If no such node exists, returns undefined.
* @param {(node: any, name: string, number: number | null) => boolean} predicate
* @internal Unstable API. Don't use in plugins for now.
*/
findAncestor(predicate) {
let stackPointer = this.stack.length - 1;
let name = null;
let node = this.stack[stackPointer--];
while (node) {
// skip index/array
let number = null;
if (typeof name === "number") {
number = name;
name = this.stack[stackPointer--];
node = this.stack[stackPointer--];
}
if (name !== null && predicate(node, name, number)) {
return node;
}
name = this.stack[stackPointer--];
node = this.stack[stackPointer--];
}
}
}
module.exports = AstPath;