Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Store indent descriptors in a plain array #17148

Merged
merged 2 commits into from May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 38 additions & 54 deletions lib/rules/indent.js
Expand Up @@ -12,8 +12,6 @@
// Requirements
//------------------------------------------------------------------------------

const { OrderedMap } = require("js-sdsl");

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -125,43 +123,48 @@ const KNOWN_NODES = new Set([


/**
* A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique.
* This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation
* A mutable map that stores (key, value) pairs. The keys are numeric indices, and must be unique.
* This is intended to be a generic wrapper around a map with non-negative integer keys, so that the underlying implementation
* can easily be swapped out.
*/
class BinarySearchTree {
class IndexMap {

/**
* Creates an empty tree
* Creates an empty map
* @param {number} maxKey The maximum key
*/
constructor() {
this._orderedMap = new OrderedMap();
this._orderedMapEnd = this._orderedMap.end();
constructor(maxKey) {

// Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance.
this._values = Array(maxKey + 1);
}

/**
* Inserts an entry into the tree.
* Inserts an entry into the map.
* @param {number} key The entry's key
* @param {any} value The entry's value
* @returns {void}
*/
insert(key, value) {
this._orderedMap.setElement(key, value);
this._values[key] = value;
}

/**
* Finds the entry with the largest key less than or equal to the provided key
* Finds the value of the entry with the largest key less than or equal to the provided key
* @param {number} key The provided key
* @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists.
* @returns {*|undefined} The value of the found entry, or undefined if no such entry exists.
*/
findLe(key) {
const iterator = this._orderedMap.reverseLowerBound(key);
findLastNotAfter(key) {
const values = this._values;

if (iterator.equals(this._orderedMapEnd)) {
return {};
}
for (let index = key; index >= 0; index--) {
const value = values[index];

return { key: iterator.pointer[0], value: iterator.pointer[1] };
if (value) {
return value;
}
}
return void 0;
}

/**
Expand All @@ -171,26 +174,7 @@ class BinarySearchTree {
* @returns {void}
*/
deleteRange(start, end) {

// Exit without traversing the tree if the range has zero size.
if (start === end) {
return;
}
const iterator = this._orderedMap.lowerBound(start);

if (iterator.equals(this._orderedMapEnd)) {
return;
}

if (end > this._orderedMap.back()[0]) {
while (!iterator.equals(this._orderedMapEnd)) {
this._orderedMap.eraseElementByIterator(iterator);
}
} else {
while (iterator.pointer[0] < end) {
this._orderedMap.eraseElementByIterator(iterator);
}
}
this._values.fill(void 0, start, end);
}
}

Expand Down Expand Up @@ -252,22 +236,23 @@ class OffsetStorage {
* @param {TokenInfo} tokenInfo a TokenInfo instance
* @param {number} indentSize The desired size of each indentation level
* @param {string} indentType The indentation character
* @param {number} maxIndex The maximum end index of any token
*/
constructor(tokenInfo, indentSize, indentType) {
constructor(tokenInfo, indentSize, indentType, maxIndex) {
this._tokenInfo = tokenInfo;
this._indentSize = indentSize;
this._indentType = indentType;

this._tree = new BinarySearchTree();
this._tree.insert(0, { offset: 0, from: null, force: false });
this._indexMap = new IndexMap(maxIndex);
this._indexMap.insert(0, { offset: 0, from: null, force: false });

this._lockedFirstTokens = new WeakMap();
this._desiredIndentCache = new WeakMap();
this._ignoredTokens = new WeakSet();
}

_getOffsetDescriptor(token) {
return this._tree.findLe(token.range[0]).value;
return this._indexMap.findLastNotAfter(token.range[0]);
}

/**
Expand Down Expand Up @@ -388,37 +373,36 @@ class OffsetStorage {
* * key: 820, value: { offset: 1, from: bazToken }
*
* To find the offset descriptor for any given token, one needs to find the node with the largest key
* which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary
* search tree indexed by key.
* which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key.
*/

const descriptorToInsert = { offset, from: fromToken, force };

const descriptorAfterRange = this._tree.findLe(range[1]).value;
const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]);

const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1];
const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken);

// First, remove any existing nodes in the range from the tree.
this._tree.deleteRange(range[0] + 1, range[1]);
// First, remove any existing nodes in the range from the map.
this._indexMap.deleteRange(range[0] + 1, range[1]);

// Insert a new node into the tree for this range
this._tree.insert(range[0], descriptorToInsert);
// Insert a new node into the map for this range
this._indexMap.insert(range[0], descriptorToInsert);

/*
* To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously,
* even if it's in the current range.
*/
if (fromTokenIsInRange) {
this._tree.insert(fromToken.range[0], fromTokenDescriptor);
this._tree.insert(fromToken.range[1], descriptorToInsert);
this._indexMap.insert(fromToken.range[0], fromTokenDescriptor);
this._indexMap.insert(fromToken.range[1], descriptorToInsert);
}

/*
* To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following
* tokens the same as it was before.
*/
this._tree.insert(range[1], descriptorAfterRange);
this._indexMap.insert(range[1], descriptorAfterRange);
}

/**
Expand Down Expand Up @@ -705,7 +689,7 @@ module.exports = {

const sourceCode = context.sourceCode;
const tokenInfo = new TokenInfo(sourceCode);
const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t");
const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t", sourceCode.text.length);
const parameterParens = new WeakSet();

/**
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -89,7 +89,6 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-sdsl": "^4.1.4",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
Expand Down