Use efficient filters
This commit is contained in:
parent
07cb8491e2
commit
3f2ecfb0b6
13 changed files with 82 additions and 47 deletions
|
|
@ -55,6 +55,25 @@ export default [
|
|||
message: "Use replaceAll instead of replace to replace all occurrences of a substring."
|
||||
}
|
||||
],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='splice'][arguments.length=2][arguments.1.type='Literal'][arguments.1.value=1]",
|
||||
message: "Use `removeFromArray(array, item)` instead of manually using indexOf + splice(index, 1). Import from 'sync-client/src/utils/remove-from-array'."
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='filter'] > ArrowFunctionExpression[body.type='BinaryExpression'][body.operator='!==']",
|
||||
message: "Use `removeFromArray(array, item)` instead of filter(x => x !== item) for better performance. Import from 'sync-client/src/utils/remove-from-array'."
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='filter'] > ArrowFunctionExpression > BlockStatement > ReturnStatement > BinaryExpression[operator='!==']",
|
||||
message: "Use `removeFromArray(array, item)` instead of filter(x => { return x !== item }) for better performance. Import from 'sync-client/src/utils/remove-from-array'."
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='filter'] > FunctionExpression[body.type='BlockStatement'] > BlockStatement > ReturnStatement > BinaryExpression[operator='!==']",
|
||||
message: "Use `removeFromArray(array, item)` instead of filter(function(x) { return x !== item }) for better performance. Import from 'sync-client/src/utils/remove-from-array'."
|
||||
}
|
||||
],
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ import type {
|
|||
NetworkConnectionStatus,
|
||||
SyncClient
|
||||
} from "sync-client";
|
||||
import { utils } from "sync-client";
|
||||
|
||||
export class StatusDescription {
|
||||
private lastHistoryStats: HistoryStats | undefined;
|
||||
private lastRemaining: number | undefined;
|
||||
private lastConnectionState: NetworkConnectionStatus | undefined;
|
||||
|
||||
private statusChangeListeners: (() => unknown)[] = [];
|
||||
private readonly statusChangeListeners: (() => unknown)[] = [];
|
||||
|
||||
public constructor(private readonly syncClient: SyncClient) {
|
||||
void this.updateConnectionState();
|
||||
|
|
@ -46,9 +47,7 @@ export class StatusDescription {
|
|||
this.statusChangeListeners.push(listener);
|
||||
}
|
||||
public removeStatusChangeListener(listener: () => unknown): void {
|
||||
this.statusChangeListeners = this.statusChangeListeners.filter(
|
||||
(l) => l !== listener
|
||||
);
|
||||
utils.removeFromArray(this.statusChangeListeners, listener);
|
||||
}
|
||||
|
||||
public renderStatusDescription(container: HTMLElement): void {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { slowWebSocketFactory } from "./utils/debugging/slow-web-socket-factory"
|
|||
import { getRandomColor } from "./utils/get-random-color";
|
||||
import { lineAndColumnToPosition } from "./utils/line-and-column-to-position";
|
||||
import { positionToLineAndColumn } from "./utils/position-to-line-and-column";
|
||||
import { removeFromArray } from "./utils/remove-from-array";
|
||||
|
||||
export {
|
||||
SyncType,
|
||||
|
|
@ -43,5 +44,6 @@ export const utils = {
|
|||
getRandomColor,
|
||||
positionToLineAndColumn,
|
||||
lineAndColumnToPosition,
|
||||
awaitAll
|
||||
awaitAll,
|
||||
removeFromArray
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { Logger } from "../tracing/logger";
|
|||
import { EMPTY_HASH } from "../utils/hash";
|
||||
import { CoveredValues } from "../utils/data-structures/min-covered";
|
||||
import { awaitAll } from "../utils/await-all";
|
||||
import { removeFromArray } from "../utils/remove-from-array";
|
||||
|
||||
export type VaultUpdateId = number;
|
||||
export type DocumentId = string;
|
||||
|
|
@ -93,6 +94,7 @@ export class Database {
|
|||
public get resolvedDocuments(): DocumentRecord[] {
|
||||
const paths = new Map<string, DocumentRecord[]>();
|
||||
this.documents
|
||||
// eslint-disable-next-line no-restricted-syntax -- Type narrowing, not removing a specific item
|
||||
.filter(({ metadata }) => metadata !== undefined)
|
||||
.forEach((record) =>
|
||||
paths.set(record.relativePath, [
|
||||
|
|
@ -151,12 +153,12 @@ export class Database {
|
|||
return;
|
||||
}
|
||||
|
||||
entry.updates = entry.updates.filter((update) => update !== promise);
|
||||
removeFromArray(entry.updates, promise);
|
||||
// No need to save as Promises don't get serialized
|
||||
}
|
||||
|
||||
public removeDocument(find: DocumentRecord): void {
|
||||
this.documents = this.documents.filter((document) => document !== find);
|
||||
removeFromArray(this.documents, find);
|
||||
this.saveInTheBackground();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { Logger } from "../tracing/logger";
|
||||
import { awaitAll } from "../utils/await-all";
|
||||
import { Lock } from "../utils/data-structures/locks";
|
||||
import { removeFromArray } from "../utils/remove-from-array";
|
||||
|
||||
export interface SyncSettings {
|
||||
remoteUri: string;
|
||||
|
|
@ -69,10 +70,7 @@ export class Settings {
|
|||
public removeOnSettingsChangeListener(
|
||||
listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown
|
||||
): void {
|
||||
const index = this.onSettingsChangeHandlers.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
this.onSettingsChangeHandlers.splice(index, 1);
|
||||
}
|
||||
removeFromArray(this.onSettingsChangeHandlers, listener);
|
||||
}
|
||||
|
||||
public async setSetting<T extends keyof SyncSettings>(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { createPromise } from "../utils/create-promise";
|
|||
import type { WebSocketVaultUpdate } from "./types/WebSocketVaultUpdate";
|
||||
import { awaitAll } from "../utils/await-all";
|
||||
import { WEBSOCKET_DISCONNECT_TIMEOUT_IN_S } from "../consts";
|
||||
import { removeFromArray } from "../utils/remove-from-array";
|
||||
|
||||
export class WebSocketManager {
|
||||
private readonly webSocketStatusChangeListeners: ((
|
||||
|
|
@ -227,12 +228,10 @@ export class WebSocketManager {
|
|||
);
|
||||
})
|
||||
.finally(() => {
|
||||
const index = this.outstandingPromises.indexOf(
|
||||
removeFromArray(
|
||||
this.outstandingPromises,
|
||||
messageHandlingPromise
|
||||
);
|
||||
if (index !== -1) {
|
||||
void this.outstandingPromises.splice(index, 1); // ignore the returned promise
|
||||
}
|
||||
});
|
||||
|
||||
void this.outstandingPromises.push(messageHandlingPromise); // ignore the returned promise
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { RelativePath } from "../persistence/database";
|
||||
import { removeFromArray } from "../utils/remove-from-array";
|
||||
|
||||
export class FileChangeNotifier {
|
||||
private readonly listeners: ((filePath: RelativePath) => unknown)[] = [];
|
||||
|
|
@ -12,10 +13,7 @@ export class FileChangeNotifier {
|
|||
public removeFileChangeListener(
|
||||
listener: (filePath: RelativePath) => unknown
|
||||
): void {
|
||||
const index = this.listeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
this.listeners.splice(index, 1);
|
||||
}
|
||||
removeFromArray(this.listeners, listener);
|
||||
}
|
||||
|
||||
public notifyOfFileChange(filePath: RelativePath): void {
|
||||
|
|
|
|||
|
|
@ -444,11 +444,13 @@ export class Syncer {
|
|||
);
|
||||
if (originalFile !== undefined) {
|
||||
// `originalFile` hasn't been deleted but it got moved instead
|
||||
/* eslint-disable no-restricted-syntax -- Comparing by property, not direct equality */
|
||||
locallyPossiblyDeletedFiles =
|
||||
locallyPossiblyDeletedFiles.filter(
|
||||
(item) =>
|
||||
item.relativePath !== originalFile.relativePath
|
||||
);
|
||||
/* eslint-enable no-restricted-syntax */
|
||||
|
||||
this.logger.debug(
|
||||
`Document '${originalFile.relativePath}' was not found under its current path in the database but was found under a different path (${relativePath}), scheduling sync to move it`
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { MAX_LOG_MESSAGE_COUNT } from "../consts";
|
||||
import { removeFromArray } from "../utils/remove-from-array";
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = "DEBUG",
|
||||
|
|
@ -63,10 +64,7 @@ export class Logger {
|
|||
public removeOnMessageListener(
|
||||
listener: (message: LogLine) => unknown
|
||||
): void {
|
||||
const index = this.onMessageListeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
this.onMessageListeners.splice(index, 1);
|
||||
}
|
||||
removeFromArray(this.onMessageListeners, listener);
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
} from "../consts";
|
||||
import type { RelativePath } from "../persistence/database";
|
||||
import type { Logger } from "./logger";
|
||||
import { removeFromArray } from "../utils/remove-from-array";
|
||||
|
||||
export interface SyncCreateDetails {
|
||||
type: SyncType.CREATE;
|
||||
|
|
@ -68,7 +69,7 @@ export interface HistoryStats {
|
|||
}
|
||||
|
||||
export class SyncHistory {
|
||||
private _entries: HistoryEntry[] = [];
|
||||
private readonly _entries: HistoryEntry[] = [];
|
||||
|
||||
private readonly syncHistoryUpdateListeners: ((
|
||||
status: HistoryStats
|
||||
|
|
@ -99,7 +100,7 @@ export class SyncHistory {
|
|||
|
||||
const candidate = this.findSimilarRecentUpdateEntry(historyEntry);
|
||||
if (candidate !== undefined) {
|
||||
this._entries = this._entries.filter((e) => e !== candidate);
|
||||
removeFromArray(this._entries, candidate);
|
||||
}
|
||||
|
||||
// Insert the entry at the beginning
|
||||
|
|
@ -122,10 +123,7 @@ export class SyncHistory {
|
|||
public removeSyncHistoryUpdateListener(
|
||||
listener: (stats: HistoryStats) => unknown
|
||||
): void {
|
||||
const index = this.syncHistoryUpdateListeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
this.syncHistoryUpdateListeners.splice(index, 1);
|
||||
}
|
||||
removeFromArray(this.syncHistoryUpdateListeners, listener);
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
|
|
|
|||
|
|
@ -2,17 +2,20 @@ import { makeRe } from "minimatch";
|
|||
import type { Logger } from "../tracing/logger";
|
||||
|
||||
export function globsToRegexes(globs: string[], logger: Logger): RegExp[] {
|
||||
return globs
|
||||
.map((pattern) => {
|
||||
const result = makeRe(pattern, {
|
||||
dot: true
|
||||
});
|
||||
if (result === false) {
|
||||
logger.warn(
|
||||
`Failed to parse ${pattern}' as a glob pattern, skipping it`
|
||||
);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.filter((pattern) => pattern !== false);
|
||||
return (
|
||||
globs
|
||||
.map((pattern) => {
|
||||
const result = makeRe(pattern, {
|
||||
dot: true
|
||||
});
|
||||
if (result === false) {
|
||||
logger.warn(
|
||||
`Failed to parse ${pattern}' as a glob pattern, skipping it`
|
||||
);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
// eslint-disable-next-line no-restricted-syntax -- Filtering out false values, not removing a specific item
|
||||
.filter((pattern) => pattern !== false)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
17
frontend/sync-client/src/utils/remove-from-array.ts
Normal file
17
frontend/sync-client/src/utils/remove-from-array.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Efficiently removes a specific item from an array by modifying it in place.
|
||||
* This is more efficient than using `.filter(item => item !== toRemove)` as it avoids creating a new array
|
||||
*
|
||||
* @param array The array to modify
|
||||
* @param item The item to remove
|
||||
* @returns true if the item was found and removed, false otherwise
|
||||
*/
|
||||
export function removeFromArray<T>(array: T[], item: T): boolean {
|
||||
const index = array.indexOf(item);
|
||||
if (index !== -1) {
|
||||
// eslint-disable-next-line no-restricted-syntax -- This is the implementation of the helper itself
|
||||
array.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ export class MockAgent extends MockClient {
|
|||
private readonly pendingActions: Promise<unknown>[] = [];
|
||||
|
||||
// The renamed file finding algorithm isn't too smart so we can't both update and rename the same file
|
||||
private doNotTouchWhileOffline: string[] = [];
|
||||
private readonly doNotTouchWhileOffline: string[] = [];
|
||||
|
||||
public constructor(
|
||||
initialSettings: Partial<SyncSettings>,
|
||||
|
|
@ -54,10 +54,10 @@ export class MockAgent extends MockClient {
|
|||
);
|
||||
|
||||
if (historyEntry) {
|
||||
this.doNotTouchWhileOffline =
|
||||
this.doNotTouchWhileOffline.filter(
|
||||
(file) => file !== historyEntry[1]
|
||||
);
|
||||
utils.removeFromArray(
|
||||
this.doNotTouchWhileOffline,
|
||||
historyEntry[1]
|
||||
);
|
||||
}
|
||||
switch (logLine.level) {
|
||||
case LogLevel.ERROR:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue