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."
|
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": [
|
"unused-imports/no-unused-vars": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ import type {
|
||||||
NetworkConnectionStatus,
|
NetworkConnectionStatus,
|
||||||
SyncClient
|
SyncClient
|
||||||
} from "sync-client";
|
} from "sync-client";
|
||||||
|
import { utils } from "sync-client";
|
||||||
|
|
||||||
export class StatusDescription {
|
export class StatusDescription {
|
||||||
private lastHistoryStats: HistoryStats | undefined;
|
private lastHistoryStats: HistoryStats | undefined;
|
||||||
private lastRemaining: number | undefined;
|
private lastRemaining: number | undefined;
|
||||||
private lastConnectionState: NetworkConnectionStatus | undefined;
|
private lastConnectionState: NetworkConnectionStatus | undefined;
|
||||||
|
|
||||||
private statusChangeListeners: (() => unknown)[] = [];
|
private readonly statusChangeListeners: (() => unknown)[] = [];
|
||||||
|
|
||||||
public constructor(private readonly syncClient: SyncClient) {
|
public constructor(private readonly syncClient: SyncClient) {
|
||||||
void this.updateConnectionState();
|
void this.updateConnectionState();
|
||||||
|
|
@ -46,9 +47,7 @@ export class StatusDescription {
|
||||||
this.statusChangeListeners.push(listener);
|
this.statusChangeListeners.push(listener);
|
||||||
}
|
}
|
||||||
public removeStatusChangeListener(listener: () => unknown): void {
|
public removeStatusChangeListener(listener: () => unknown): void {
|
||||||
this.statusChangeListeners = this.statusChangeListeners.filter(
|
utils.removeFromArray(this.statusChangeListeners, listener);
|
||||||
(l) => l !== listener
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderStatusDescription(container: HTMLElement): void {
|
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 { getRandomColor } from "./utils/get-random-color";
|
||||||
import { lineAndColumnToPosition } from "./utils/line-and-column-to-position";
|
import { lineAndColumnToPosition } from "./utils/line-and-column-to-position";
|
||||||
import { positionToLineAndColumn } from "./utils/position-to-line-and-column";
|
import { positionToLineAndColumn } from "./utils/position-to-line-and-column";
|
||||||
|
import { removeFromArray } from "./utils/remove-from-array";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SyncType,
|
SyncType,
|
||||||
|
|
@ -43,5 +44,6 @@ export const utils = {
|
||||||
getRandomColor,
|
getRandomColor,
|
||||||
positionToLineAndColumn,
|
positionToLineAndColumn,
|
||||||
lineAndColumnToPosition,
|
lineAndColumnToPosition,
|
||||||
awaitAll
|
awaitAll,
|
||||||
|
removeFromArray
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { Logger } from "../tracing/logger";
|
||||||
import { EMPTY_HASH } from "../utils/hash";
|
import { EMPTY_HASH } from "../utils/hash";
|
||||||
import { CoveredValues } from "../utils/data-structures/min-covered";
|
import { CoveredValues } from "../utils/data-structures/min-covered";
|
||||||
import { awaitAll } from "../utils/await-all";
|
import { awaitAll } from "../utils/await-all";
|
||||||
|
import { removeFromArray } from "../utils/remove-from-array";
|
||||||
|
|
||||||
export type VaultUpdateId = number;
|
export type VaultUpdateId = number;
|
||||||
export type DocumentId = string;
|
export type DocumentId = string;
|
||||||
|
|
@ -93,6 +94,7 @@ export class Database {
|
||||||
public get resolvedDocuments(): DocumentRecord[] {
|
public get resolvedDocuments(): DocumentRecord[] {
|
||||||
const paths = new Map<string, DocumentRecord[]>();
|
const paths = new Map<string, DocumentRecord[]>();
|
||||||
this.documents
|
this.documents
|
||||||
|
// eslint-disable-next-line no-restricted-syntax -- Type narrowing, not removing a specific item
|
||||||
.filter(({ metadata }) => metadata !== undefined)
|
.filter(({ metadata }) => metadata !== undefined)
|
||||||
.forEach((record) =>
|
.forEach((record) =>
|
||||||
paths.set(record.relativePath, [
|
paths.set(record.relativePath, [
|
||||||
|
|
@ -151,12 +153,12 @@ export class Database {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.updates = entry.updates.filter((update) => update !== promise);
|
removeFromArray(entry.updates, promise);
|
||||||
// No need to save as Promises don't get serialized
|
// No need to save as Promises don't get serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeDocument(find: DocumentRecord): void {
|
public removeDocument(find: DocumentRecord): void {
|
||||||
this.documents = this.documents.filter((document) => document !== find);
|
removeFromArray(this.documents, find);
|
||||||
this.saveInTheBackground();
|
this.saveInTheBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Logger } from "../tracing/logger";
|
import type { Logger } from "../tracing/logger";
|
||||||
import { awaitAll } from "../utils/await-all";
|
import { awaitAll } from "../utils/await-all";
|
||||||
import { Lock } from "../utils/data-structures/locks";
|
import { Lock } from "../utils/data-structures/locks";
|
||||||
|
import { removeFromArray } from "../utils/remove-from-array";
|
||||||
|
|
||||||
export interface SyncSettings {
|
export interface SyncSettings {
|
||||||
remoteUri: string;
|
remoteUri: string;
|
||||||
|
|
@ -69,10 +70,7 @@ export class Settings {
|
||||||
public removeOnSettingsChangeListener(
|
public removeOnSettingsChangeListener(
|
||||||
listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown
|
listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown
|
||||||
): void {
|
): void {
|
||||||
const index = this.onSettingsChangeHandlers.indexOf(listener);
|
removeFromArray(this.onSettingsChangeHandlers, listener);
|
||||||
if (index !== -1) {
|
|
||||||
this.onSettingsChangeHandlers.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setSetting<T extends keyof SyncSettings>(
|
public async setSetting<T extends keyof SyncSettings>(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { createPromise } from "../utils/create-promise";
|
||||||
import type { WebSocketVaultUpdate } from "./types/WebSocketVaultUpdate";
|
import type { WebSocketVaultUpdate } from "./types/WebSocketVaultUpdate";
|
||||||
import { awaitAll } from "../utils/await-all";
|
import { awaitAll } from "../utils/await-all";
|
||||||
import { WEBSOCKET_DISCONNECT_TIMEOUT_IN_S } from "../consts";
|
import { WEBSOCKET_DISCONNECT_TIMEOUT_IN_S } from "../consts";
|
||||||
|
import { removeFromArray } from "../utils/remove-from-array";
|
||||||
|
|
||||||
export class WebSocketManager {
|
export class WebSocketManager {
|
||||||
private readonly webSocketStatusChangeListeners: ((
|
private readonly webSocketStatusChangeListeners: ((
|
||||||
|
|
@ -227,12 +228,10 @@ export class WebSocketManager {
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
const index = this.outstandingPromises.indexOf(
|
removeFromArray(
|
||||||
|
this.outstandingPromises,
|
||||||
messageHandlingPromise
|
messageHandlingPromise
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
|
||||||
void this.outstandingPromises.splice(index, 1); // ignore the returned promise
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
void this.outstandingPromises.push(messageHandlingPromise); // ignore the returned promise
|
void this.outstandingPromises.push(messageHandlingPromise); // ignore the returned promise
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { RelativePath } from "../persistence/database";
|
import type { RelativePath } from "../persistence/database";
|
||||||
|
import { removeFromArray } from "../utils/remove-from-array";
|
||||||
|
|
||||||
export class FileChangeNotifier {
|
export class FileChangeNotifier {
|
||||||
private readonly listeners: ((filePath: RelativePath) => unknown)[] = [];
|
private readonly listeners: ((filePath: RelativePath) => unknown)[] = [];
|
||||||
|
|
@ -12,10 +13,7 @@ export class FileChangeNotifier {
|
||||||
public removeFileChangeListener(
|
public removeFileChangeListener(
|
||||||
listener: (filePath: RelativePath) => unknown
|
listener: (filePath: RelativePath) => unknown
|
||||||
): void {
|
): void {
|
||||||
const index = this.listeners.indexOf(listener);
|
removeFromArray(this.listeners, listener);
|
||||||
if (index !== -1) {
|
|
||||||
this.listeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public notifyOfFileChange(filePath: RelativePath): void {
|
public notifyOfFileChange(filePath: RelativePath): void {
|
||||||
|
|
|
||||||
|
|
@ -444,11 +444,13 @@ export class Syncer {
|
||||||
);
|
);
|
||||||
if (originalFile !== undefined) {
|
if (originalFile !== undefined) {
|
||||||
// `originalFile` hasn't been deleted but it got moved instead
|
// `originalFile` hasn't been deleted but it got moved instead
|
||||||
|
/* eslint-disable no-restricted-syntax -- Comparing by property, not direct equality */
|
||||||
locallyPossiblyDeletedFiles =
|
locallyPossiblyDeletedFiles =
|
||||||
locallyPossiblyDeletedFiles.filter(
|
locallyPossiblyDeletedFiles.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.relativePath !== originalFile.relativePath
|
item.relativePath !== originalFile.relativePath
|
||||||
);
|
);
|
||||||
|
/* eslint-enable no-restricted-syntax */
|
||||||
|
|
||||||
this.logger.debug(
|
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`
|
`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 { MAX_LOG_MESSAGE_COUNT } from "../consts";
|
||||||
|
import { removeFromArray } from "../utils/remove-from-array";
|
||||||
|
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
DEBUG = "DEBUG",
|
DEBUG = "DEBUG",
|
||||||
|
|
@ -63,10 +64,7 @@ export class Logger {
|
||||||
public removeOnMessageListener(
|
public removeOnMessageListener(
|
||||||
listener: (message: LogLine) => unknown
|
listener: (message: LogLine) => unknown
|
||||||
): void {
|
): void {
|
||||||
const index = this.onMessageListeners.indexOf(listener);
|
removeFromArray(this.onMessageListeners, listener);
|
||||||
if (index !== -1) {
|
|
||||||
this.onMessageListeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
} from "../consts";
|
} from "../consts";
|
||||||
import type { RelativePath } from "../persistence/database";
|
import type { RelativePath } from "../persistence/database";
|
||||||
import type { Logger } from "./logger";
|
import type { Logger } from "./logger";
|
||||||
|
import { removeFromArray } from "../utils/remove-from-array";
|
||||||
|
|
||||||
export interface SyncCreateDetails {
|
export interface SyncCreateDetails {
|
||||||
type: SyncType.CREATE;
|
type: SyncType.CREATE;
|
||||||
|
|
@ -68,7 +69,7 @@ export interface HistoryStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SyncHistory {
|
export class SyncHistory {
|
||||||
private _entries: HistoryEntry[] = [];
|
private readonly _entries: HistoryEntry[] = [];
|
||||||
|
|
||||||
private readonly syncHistoryUpdateListeners: ((
|
private readonly syncHistoryUpdateListeners: ((
|
||||||
status: HistoryStats
|
status: HistoryStats
|
||||||
|
|
@ -99,7 +100,7 @@ export class SyncHistory {
|
||||||
|
|
||||||
const candidate = this.findSimilarRecentUpdateEntry(historyEntry);
|
const candidate = this.findSimilarRecentUpdateEntry(historyEntry);
|
||||||
if (candidate !== undefined) {
|
if (candidate !== undefined) {
|
||||||
this._entries = this._entries.filter((e) => e !== candidate);
|
removeFromArray(this._entries, candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the entry at the beginning
|
// Insert the entry at the beginning
|
||||||
|
|
@ -122,10 +123,7 @@ export class SyncHistory {
|
||||||
public removeSyncHistoryUpdateListener(
|
public removeSyncHistoryUpdateListener(
|
||||||
listener: (stats: HistoryStats) => unknown
|
listener: (stats: HistoryStats) => unknown
|
||||||
): void {
|
): void {
|
||||||
const index = this.syncHistoryUpdateListeners.indexOf(listener);
|
removeFromArray(this.syncHistoryUpdateListeners, listener);
|
||||||
if (index !== -1) {
|
|
||||||
this.syncHistoryUpdateListeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,20 @@ import { makeRe } from "minimatch";
|
||||||
import type { Logger } from "../tracing/logger";
|
import type { Logger } from "../tracing/logger";
|
||||||
|
|
||||||
export function globsToRegexes(globs: string[], logger: Logger): RegExp[] {
|
export function globsToRegexes(globs: string[], logger: Logger): RegExp[] {
|
||||||
return globs
|
return (
|
||||||
.map((pattern) => {
|
globs
|
||||||
const result = makeRe(pattern, {
|
.map((pattern) => {
|
||||||
dot: true
|
const result = makeRe(pattern, {
|
||||||
});
|
dot: true
|
||||||
if (result === false) {
|
});
|
||||||
logger.warn(
|
if (result === false) {
|
||||||
`Failed to parse ${pattern}' as a glob pattern, skipping it`
|
logger.warn(
|
||||||
);
|
`Failed to parse ${pattern}' as a glob pattern, skipping it`
|
||||||
}
|
);
|
||||||
return result;
|
}
|
||||||
})
|
return result;
|
||||||
.filter((pattern) => pattern !== false);
|
})
|
||||||
|
// 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>[] = [];
|
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
|
// 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(
|
public constructor(
|
||||||
initialSettings: Partial<SyncSettings>,
|
initialSettings: Partial<SyncSettings>,
|
||||||
|
|
@ -54,10 +54,10 @@ export class MockAgent extends MockClient {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (historyEntry) {
|
if (historyEntry) {
|
||||||
this.doNotTouchWhileOffline =
|
utils.removeFromArray(
|
||||||
this.doNotTouchWhileOffline.filter(
|
this.doNotTouchWhileOffline,
|
||||||
(file) => file !== historyEntry[1]
|
historyEntry[1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
switch (logLine.level) {
|
switch (logLine.level) {
|
||||||
case LogLevel.ERROR:
|
case LogLevel.ERROR:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue