diff --git a/app/common/package.json b/app/common/package.json index b904631c500..2ab252dbb0b 100644 --- a/app/common/package.json +++ b/app/common/package.json @@ -26,7 +26,8 @@ "./src/types": "./src/types.d.ts" }, "scripts": { - "test": "vitest run" + "test": "vitest run", + "lint": "eslint . --max-warnings=0" }, "peerDependencies": { "@tanstack/query-core": "5.54.1", diff --git a/app/common/src/accessToken.ts b/app/common/src/accessToken.ts index 2a3c689994c..af670b372e5 100644 --- a/app/common/src/accessToken.ts +++ b/app/common/src/accessToken.ts @@ -4,17 +4,19 @@ // === AccessToken === // =================== -/** Payload for the `save-access-token` event that saves an access token to a credentials file. */ +/** Credentials to be saved to a credentials file. */ export interface AccessToken { - /** The JWT token to save. */ + /** The user's JWT token. */ readonly accessToken: string - /** The Cognito app integration client id. */ + /** The ID for the Cognito app integration. */ readonly clientId: string - /** The refresh token taken from authorization flow. */ + /** The refresh token taken from the authorization flow. */ readonly refreshToken: string - /** The Cognito url to refresh the token. */ + /** The Cognito URL to refresh the token. */ readonly refreshUrl: string - /** The when the token will expire. - * This is a string representation of a date in ISO 8601 format (e.g. "2021-01-01T00:00:00Z"). */ + /** + * The expiry time of the token. + * This is a string representation of a date in ISO 8601 format (e.g. "2021-01-01T00:00:00Z"). + */ readonly expireAt: string } diff --git a/app/common/src/appConfig.d.ts b/app/common/src/appConfig.d.ts index 0a8bd8e07bc..3411f03f67f 100644 --- a/app/common/src/appConfig.d.ts +++ b/app/common/src/appConfig.d.ts @@ -1,17 +1,21 @@ /** @file Functions for managing app configuration. */ -/** Read environment variables from a file based on the `ENSO_CLOUD_ENV_FILE_NAME` +/** + * Read environment variables from a file based on the `ENSO_CLOUD_ENV_FILE_NAME` * environment variable. Reads from `.env` if the variable is blank or absent. - * DOES NOT override existing environment variables if the variable is absent. */ + * DOES NOT override existing environment variables if the variable is absent. + */ export function readEnvironmentFromFile(): Promise -/** An object containing app configuration to inject. +/** + * An object containing app configuration to inject. * * This includes: * - the base URL for backend endpoints * - the WebSocket URL for the chatbot * - the unique identifier for the cloud environment, for use in Sentry logs - * - Stripe, Sentry and Amplify public keys */ + * - Stripe, Sentry and Amplify public keys + */ export function getDefines(serverPort?: number): Record /** Load test environment variables, useful for when the Cloud backend is mocked or unnecessary. */ diff --git a/app/common/src/appConfig.js b/app/common/src/appConfig.js index 343f93305ce..ccde478f188 100644 --- a/app/common/src/appConfig.js +++ b/app/common/src/appConfig.js @@ -1,6 +1,7 @@ /** @file Functions for managing app configuration. */ import * as fs from 'node:fs/promises' import * as path from 'node:path' +import * as process from 'node:process' import * as url from 'node:url' // =============================== @@ -19,14 +20,13 @@ export async function readEnvironmentFromFile() { const filePath = path.join(url.fileURLToPath(new URL('../..', import.meta.url)), fileName) const buildInfo = await (async () => { try { - return await import('../../../../build.json', { with: { type: 'json' } }) + return await import('../../../build.json', { with: { type: 'json' } }) } catch { return { commit: '', version: '', engineVersion: '', name: '' } } })() try { const file = await fs.readFile(filePath, { encoding: 'utf-8' }) - // eslint-disable-next-line jsdoc/valid-types /** @type {readonly (readonly [string, string])[]} */ let entries = file.split('\n').flatMap(line => { if (/^\s*$|^.s*#/.test(line)) { @@ -47,14 +47,10 @@ export async function readEnvironmentFromFile() { if (!isProduction || entries.length > 0) { Object.assign(process.env, variables) } - // @ts-expect-error This is the only file where `process.env` should be written to. process.env.ENSO_CLOUD_DASHBOARD_VERSION ??= buildInfo.version - // @ts-expect-error This is the only file where `process.env` should be written to. process.env.ENSO_CLOUD_DASHBOARD_COMMIT_HASH ??= buildInfo.commit } catch (error) { - // @ts-expect-error This is the only file where `process.env` should be written to. process.env.ENSO_CLOUD_DASHBOARD_VERSION ??= buildInfo.version - // @ts-expect-error This is the only file where `process.env` should be written to. process.env.ENSO_CLOUD_DASHBOARD_COMMIT_HASH ??= buildInfo.commit const expectedKeys = Object.keys(DUMMY_DEFINES) .map(key => key.replace(/^process[.]env[.]/, '')) @@ -147,7 +143,6 @@ const DUMMY_DEFINES = { /** Load test environment variables, useful for when the Cloud backend is mocked or unnecessary. */ export function loadTestEnvironmentVariables() { for (const [k, v] of Object.entries(DUMMY_DEFINES)) { - // @ts-expect-error This is the only file where `process.env` should be written to. process.env[k.replace(/^process[.]env[.]/, '')] = v } } diff --git a/app/common/src/buildUtils.d.ts b/app/common/src/buildUtils.d.ts index 0223a7eb41a..8bf7f411d45 100644 --- a/app/common/src/buildUtils.d.ts +++ b/app/common/src/buildUtils.d.ts @@ -3,26 +3,34 @@ /** Indent size for stringifying JSON. */ export const INDENT_SIZE: number -/** Get the environment variable value. +/** + * Get the environment variable value. * @param name - The name of the environment variable. * @returns The value of the environment variable. - * @throws {Error} If the environment variable is not set. */ + * @throws {Error} If the environment variable is not set. + */ export function requireEnv(name: string): string -/** Read the path from environment variable and resolve it. +/** + * Read the path from environment variable and resolve it. * @param name - The name of the environment variable. * @returns The resolved path. - * @throws {Error} If the environment variable is not set. */ + * @throws {Error} If the environment variable is not set. + */ export function requireEnvResolvedPath(name: string): string -/** Read the path from environment variable and resolve it. Verify that it exists. +/** + * Read the path from environment variable and resolve it. Verify that it exists. * @param name - The name of the environment variable. * @returns The resolved path. - * @throws {Error} If the environment variable is not set or path does not exist. */ + * @throws {Error} If the environment variable is not set or path does not exist. + */ export function requireEnvPathExist(name: string): string -/** Get the common prefix of the two strings. +/** + * Get the common prefix of the two strings. * @param a - the first string. * @param b - the second string. - * @returns The common prefix. */ + * @returns The common prefix. + */ export function getCommonPrefix(a: string, b: string): string diff --git a/app/common/src/detect.ts b/app/common/src/detect.ts index df22a2a1258..f6f0fd428e6 100644 --- a/app/common/src/detect.ts +++ b/app/common/src/detect.ts @@ -23,8 +23,10 @@ export enum Platform { android = 'Android', } -/** The platform the app is currently running on. - * This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. */ +/** + * The platform the app is currently running on. + * This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. + */ export function platform() { if (isOnWindowsPhone()) { // MUST be before Android and Windows. @@ -96,8 +98,10 @@ export enum Browser { opera = 'Opera', } -/** Return the platform the app is currently running on. - * This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. */ +/** + * Return the platform the app is currently running on. + * This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. + */ export function browser(): Browser { if (isOnElectron()) { return Browser.electron @@ -117,10 +121,12 @@ export function browser(): Browser { return Browser.unknown } } -/** Returns `true` if running in Electron, else `false`. +/** + * Returns `true` if running in Electron, else `false`. * This is used to determine whether to use a `MemoryRouter` (stores history in an array) * or a `BrowserRouter` (stores history in the path of the URL). - * It is also used to determine whether to send custom state to Amplify for a workaround. */ + * It is also used to determine whether to send custom state to Amplify for a workaround. + */ export function isOnElectron() { return /electron/i.test(navigator.userAgent) } diff --git a/app/common/src/gtag.ts b/app/common/src/gtag.ts index d921c674740..5f02671e209 100644 --- a/app/common/src/gtag.ts +++ b/app/common/src/gtag.ts @@ -17,7 +17,7 @@ window.dataLayer = window.dataLayer || [] export function gtag(_action: 'config' | 'event' | 'js' | 'set', ..._args: unknown[]) { // @ts-expect-error This is explicitly not given types as it is a mistake to acess this // anywhere else. - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, prefer-rest-params window.dataLayer.push(arguments) } @@ -27,7 +27,7 @@ export function event(name: string, params?: object) { } gtag('js', new Date()) -// eslint-disable-next-line @typescript-eslint/naming-convention +// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase gtag('set', 'linker', { accept_incoming: true }) gtag('config', GOOGLE_ANALYTICS_TAG) if (GOOGLE_ANALYTICS_TAG === 'G-CLTBJ37MDM') { diff --git a/app/common/src/index.d.ts b/app/common/src/index.d.ts index 69e541e772a..d6604aa3e4e 100644 --- a/app/common/src/index.d.ts +++ b/app/common/src/index.d.ts @@ -1,18 +1,22 @@ -/** @file This module contains metadata about the product and distribution, +/** + * @file This module contains metadata about the product and distribution, * and various other constants that are needed in multiple sibling packages. * * Code in this package is used by two or more sibling packages of this package. The code is defined * here when it is not possible for a sibling package to own that code without introducing a - * circular dependency in our packages. */ + * circular dependency in our packages. + */ // ======================== // === Product metadata === // ======================== -/** URL protocol scheme for deep links to authentication flow pages, without the `:` suffix. +/** + * URL protocol scheme for deep links to authentication flow pages, without the `:` suffix. * * For example: the deep link URL - * `enso://authentication/register?code=...&state=...` uses this scheme. */ + * `enso://authentication/register?code=...&state=...` uses this scheme. + */ export const DEEP_LINK_SCHEME: string /** Name of the product. */ @@ -21,12 +25,16 @@ export const PRODUCT_NAME: string /** Company name, used as the copyright holder. */ export const COMPANY_NAME: string -/** The domain on which the Cloud Dashboard web app is hosted. - * Excludes the protocol (`https://`). */ +/** + * The domain on which the Cloud Dashboard web app is hosted. + * Excludes the protocol (`https://`). + */ export const CLOUD_DASHBOARD_DOMAIN: string -/** COOP, COEP, and CORP headers: https://web.dev/coop-coep/ +/** + * COOP, COEP, and CORP headers: https://web.dev/coop-coep/ * * These are required to increase the resolution of `performance.now()` timers, - * making profiling a lot more accurate and consistent. */ + * making profiling a lot more accurate and consistent. + */ export const COOP_COEP_CORP_HEADERS: [header: string, value: string][] diff --git a/app/common/src/queryClient.ts b/app/common/src/queryClient.ts index fcff13e5dfb..0c049c0c54d 100644 --- a/app/common/src/queryClient.ts +++ b/app/common/src/queryClient.ts @@ -10,9 +10,7 @@ import * as vueQuery from '@tanstack/vue-query' import * as idbKeyval from 'idb-keyval' declare module '@tanstack/query-core' { - /** - * Query client with additional methods. - */ + /** Query client with additional methods. */ interface QueryClient { /** * Clear the cache stored in Tanstack Query and the persister storage. @@ -20,19 +18,13 @@ declare module '@tanstack/query-core' { * Usually you should use `queryClient.invalidateQueries` instead. */ readonly clearWithPersister: () => Promise - /** - * Clear the cache stored in the persister storage. - */ + /** Clear the cache stored in the persister storage. */ readonly nukePersister: () => Promise } - /** - * Specifies the invalidation behavior of a mutation. - */ + /** Specifies the invalidation behavior of a mutation. */ interface Register { readonly mutationMeta: { - /** - * List of query keys to invalidate when the mutation succeeds. - */ + /** List of query keys to invalidate when the mutation succeeds. */ readonly invalidates?: queryCore.QueryKey[] /** * List of query keys to await invalidation before the mutation is considered successful. @@ -71,9 +63,7 @@ const DEFAULT_QUERY_PERSIST_TIME_MS = 30 * 24 * 60 * 60 * 1000 // 30 days const DEFAULT_BUSTER = 'v1.1' -/** - * Create a new Tanstack Query client. - */ +/** Create a new Tanstack Query client. */ export function createQueryClient(): QueryClient { const store = idbKeyval.createStore('enso', 'query-persist-cache') queryCore.onlineManager.setOnline(navigator.onLine) diff --git a/app/common/src/services/Backend.ts b/app/common/src/services/Backend.ts index f7658ad4221..0f901261d94 100644 --- a/app/common/src/services/Backend.ts +++ b/app/common/src/services/Backend.ts @@ -29,8 +29,10 @@ export const UserGroupId = newtype.newtypeConstructor() export type DirectoryId = newtype.Newtype export const DirectoryId = newtype.newtypeConstructor() -/** Unique identifier for an asset representing the items inside a directory for which the - * request to retrive the items has not yet completed. */ +/** + * Unique identifier for an asset representing the items inside a directory for which the + * request to retrive the items has not yet completed. + */ export type LoadingAssetId = newtype.Newtype export const LoadingAssetId = newtype.newtypeConstructor() @@ -38,8 +40,10 @@ export const LoadingAssetId = newtype.newtypeConstructor() export type EmptyAssetId = newtype.Newtype export const EmptyAssetId = newtype.newtypeConstructor() -/** Unique identifier for an asset representing the nonexistent children of a directory - * that failed to fetch. */ +/** + * Unique identifier for an asset representing the nonexistent children of a directory + * that failed to fetch. + */ export type ErrorAssetId = newtype.Newtype export const ErrorAssetId = newtype.newtypeConstructor() @@ -74,9 +78,7 @@ export type AssetId = IdType[keyof IdType] export type CheckoutSessionId = newtype.Newtype export const CheckoutSessionId = newtype.newtypeConstructor() -/** - * Unique identifier for a subscription. - */ +/** Unique identifier for a subscription. */ export type SubscriptionId = newtype.Newtype export const SubscriptionId = newtype.newtypeConstructor() @@ -129,14 +131,18 @@ export function isUserGroupId(id: string): id is UserGroupId { const PLACEHOLDER_USER_GROUP_PREFIX = 'usergroup-placeholder-' -/** Whether a given {@link UserGroupId} represents a user group that does not yet exist on the - * server. */ +/** + * Whether a given {@link UserGroupId} represents a user group that does not yet exist on the + * server. + */ export function isPlaceholderUserGroupId(id: string) { return id.startsWith(PLACEHOLDER_USER_GROUP_PREFIX) } -/** Return a new {@link UserGroupId} that represents a placeholder user group that is yet to finish - * being created on the backend. */ +/** + * Return a new {@link UserGroupId} that represents a placeholder user group that is yet to finish + * being created on the backend. + */ export function newPlaceholderUserGroupId() { return UserGroupId(`${PLACEHOLDER_USER_GROUP_PREFIX}${uniqueString.uniqueString()}`) } @@ -153,17 +159,21 @@ export enum BackendType { /** Metadata uniquely identifying a user inside an organization. */ export interface UserInfo { - /** The ID of the parent organization. If this is a sole user, they are implicitly in an - * organization consisting of only themselves. */ + /** + * The ID of the parent organization. If this is a sole user, they are implicitly in an + * organization consisting of only themselves. + */ readonly organizationId: OrganizationId /** The name of the parent organization. */ readonly organizationName?: string - /** The ID of this user. + /** + * The ID of this user. * * The user ID is globally unique. Thus, the user ID is always sufficient to uniquely identify a * user. The user ID is guaranteed to never change, once assigned. For these reasons, the user ID * should be the preferred way to uniquely refer to a user. That is, when referring to a user, - * prefer this field over `name`, `email`, `subject`, or any other mechanism, where possible. */ + * prefer this field over `name`, `email`, `subject`, or any other mechanism, where possible. + */ readonly userId: UserId readonly name: string readonly email: EmailAddress @@ -173,8 +183,10 @@ export interface UserInfo { /** A user in the application. These are the primary owners of a project. */ export interface User extends UserInfo { - /** If `false`, this account is awaiting acceptance from an administrator, and endpoints other than - * `usersMe` will not work. */ + /** + * If `false`, this account is awaiting acceptance from an administrator, and endpoints other than + * `usersMe` will not work. + */ readonly isEnabled: boolean readonly isOrganizationAdmin: boolean readonly rootDirectoryId: DirectoryId @@ -200,11 +212,15 @@ export enum ProjectState { provisioned = 'Provisioned', opened = 'Opened', closed = 'Closed', - /** A frontend-specific state, representing a project that should be displayed as - * `openInProgress`, but has not yet been added to the backend. */ + /** + * A frontend-specific state, representing a project that should be displayed as + * `openInProgress`, but has not yet been added to the backend. + */ placeholder = 'Placeholder', - /** A frontend-specific state, representing a project that should be displayed as `closed`, - * but is still in the process of shutting down. */ + /** + * A frontend-specific state, representing a project that should be displayed as `closed`, + * but is still in the process of shutting down. + */ closing = 'Closing', } @@ -374,11 +390,13 @@ export interface Label { readonly color: LChColor } -/** Type of application that a {@link Version} applies to. +/** + * Type of application that a {@link Version} applies to. * * We keep track of both backend and IDE versions, so that we can update the two independently. * However the format of the version numbers is the same for both, so we can use the same type for - * both. We just need this enum to disambiguate. */ + * both. We just need this enum to disambiguate. + */ export enum VersionType { backend = 'Backend', ide = 'Ide', @@ -458,9 +476,7 @@ export interface ResourceUsage { readonly storage: number } -/** - * Metadata for a subscription. - */ +/** Metadata for a subscription. */ export interface Subscription { readonly id?: SubscriptionId readonly plan?: Plan @@ -566,7 +582,7 @@ export interface UpdatedDirectory { } /** The type returned from the "create directory" endpoint. */ -export interface Directory extends DirectoryAsset {} +export type Directory = DirectoryAsset /** The subset of asset fields returned by the "copy asset" endpoint. */ export interface CopiedAsset { @@ -711,8 +727,10 @@ export enum AssetType { secret = 'secret', datalink = 'datalink', directory = 'directory', - /** A special {@link AssetType} representing the unknown items of a directory, before the - * request to retrieve the items completes. */ + /** + * A special {@link AssetType} representing the unknown items of a directory, before the + * request to retrieve the items completes. + */ specialLoading = 'specialLoading', /** A special {@link AssetType} representing a directory listing that is empty. */ specialEmpty = 'specialEmpty', @@ -732,8 +750,10 @@ export interface IdType { readonly [AssetType.specialError]: ErrorAssetId } -/** Integers (starting from 0) corresponding to the order in which each asset type should appear - * in a directory listing. */ +/** + * Integers (starting from 0) corresponding to the order in which each asset type should appear + * in a directory listing. + */ export const ASSET_TYPE_ORDER: Readonly> = { // This is a sequence of numbers, not magic numbers. `1000` is an arbitrary number // that are higher than the number of possible asset types. @@ -753,22 +773,28 @@ export const ASSET_TYPE_ORDER: Readonly> = { // === Asset === // ============= -/** Metadata uniquely identifying a directory entry. - * These can be Projects, Files, Secrets, or other directories. */ +/** + * Metadata uniquely identifying a directory entry. + * These can be Projects, Files, Secrets, or other directories. + */ export interface BaseAsset { readonly id: AssetId readonly title: string readonly modifiedAt: dateTime.Rfc3339DateTime - /** This is defined as a generic {@link AssetId} in the backend, however it is more convenient - * (and currently safe) to assume it is always a {@link DirectoryId}. */ + /** + * This is defined as a generic {@link AssetId} in the backend, however it is more convenient + * (and currently safe) to assume it is always a {@link DirectoryId}. + */ readonly parentId: DirectoryId readonly permissions: readonly AssetPermission[] | null readonly labels: readonly LabelName[] | null readonly description: string | null } -/** Metadata uniquely identifying a directory entry. - * These can be Projects, Files, Secrets, or other directories. */ +/** + * Metadata uniquely identifying a directory entry. + * These can be Projects, Files, Secrets, or other directories. + */ export interface Asset extends BaseAsset { readonly type: Type readonly id: IdType[Type] @@ -776,31 +802,33 @@ export interface Asset extends BaseAsset { } /** A convenience alias for {@link Asset}<{@link AssetType.directory}>. */ -export interface DirectoryAsset extends Asset {} +export type DirectoryAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.project}>. */ -export interface ProjectAsset extends Asset {} +export type ProjectAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.file}>. */ -export interface FileAsset extends Asset {} +export type FileAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.datalink}>. */ -export interface DatalinkAsset extends Asset {} +export type DatalinkAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.secret}>. */ -export interface SecretAsset extends Asset {} +export type SecretAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.specialLoading}>. */ -export interface SpecialLoadingAsset extends Asset {} +export type SpecialLoadingAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.specialEmpty}>. */ -export interface SpecialEmptyAsset extends Asset {} +export type SpecialEmptyAsset = Asset /** A convenience alias for {@link Asset}<{@link AssetType.specialError}>. */ -export interface SpecialErrorAsset extends Asset {} +export type SpecialErrorAsset = Asset -/** Creates a {@link DirectoryAsset} representing the root directory for the organization, - * with all irrelevant fields initialized to default values. */ +/** + * Creates a {@link DirectoryAsset} representing the root directory for the organization, + * with all irrelevant fields initialized to default values. + */ export function createRootDirectoryAsset(directoryId: DirectoryId): DirectoryAsset { return { type: AssetType.directory, @@ -860,8 +888,10 @@ export function createPlaceholderProjectAsset( } } -/** Creates a {@link SpecialLoadingAsset}, with all irrelevant fields initialized to default - * values. */ +/** + * Creates a {@link SpecialLoadingAsset}, with all irrelevant fields initialized to default + * values. + */ export function createSpecialLoadingAsset(directoryId: DirectoryId): SpecialLoadingAsset { return { type: AssetType.specialLoading, @@ -876,8 +906,10 @@ export function createSpecialLoadingAsset(directoryId: DirectoryId): SpecialLoad } } -/** Creates a {@link SpecialEmptyAsset}, with all irrelevant fields initialized to default - * values. */ +/** + * Creates a {@link SpecialEmptyAsset}, with all irrelevant fields initialized to default + * values. + */ export function createSpecialEmptyAsset(directoryId: DirectoryId): SpecialEmptyAsset { return { type: AssetType.specialEmpty, @@ -892,8 +924,10 @@ export function createSpecialEmptyAsset(directoryId: DirectoryId): SpecialEmptyA } } -/** Creates a {@link SpecialErrorAsset}, with all irrelevant fields initialized to default - * values. */ +/** + * Creates a {@link SpecialErrorAsset}, with all irrelevant fields initialized to default + * values. + */ export function createSpecialErrorAsset(directoryId: DirectoryId): SpecialErrorAsset { return { type: AssetType.specialError, @@ -1011,8 +1045,10 @@ export interface AssetVersions { // === compareAssetPermissions === // =============================== -/** Return a positive number when `a > b`, a negative number when `a < b`, and `0` - * when `a === b`. */ +/** + * Return a positive number when `a > b`, a negative number when `a < b`, and `0` + * when `a === b`. + */ export function compareAssetPermissions(a: AssetPermission, b: AssetPermission) { const relativePermissionPrecedence = permissions.PERMISSION_ACTION_PRECEDENCE[a.permission] - @@ -1126,8 +1162,10 @@ export interface CreateProjectRequestBody { readonly datalinkId?: DatalinkId } -/** HTTP request body for the "update project" endpoint. - * Only updates of the `projectName` or `ami` are allowed. */ +/** + * HTTP request body for the "update project" endpoint. + * Only updates of the `projectName` or `ami` are allowed. + */ export interface UpdateProjectRequestBody { readonly projectName: string | null readonly ami: Ami | null @@ -1256,8 +1294,10 @@ export function compareAssets(a: AnyAsset, b: AnyAsset) { // === getAssetId === // ================== -/** A convenience function to get the `id` of an {@link Asset}. - * This is useful to avoid React re-renders as it is not re-created on each function call. */ +/** + * A convenience function to get the `id` of an {@link Asset}. + * This is useful to avoid React re-renders as it is not re-created on each function call. + */ export function getAssetId(asset: Asset) { return asset.id } @@ -1313,16 +1353,16 @@ export function stripProjectExtension(name: string) { return name.replace(/[.](?:tar[.]gz|zip|enso-project)$/, '') } -/** Return both the name and extension of the project file name (if any). - * Otherwise, returns the entire name as the basename. */ +/** + * Return both the name and extension of the project file name (if any). + * Otherwise, returns the entire name as the basename. + */ export function extractProjectExtension(name: string) { const [, basename, extension] = name.match(/^(.*)[.](tar[.]gz|zip|enso-project)$/) ?? [] return { basename: basename ?? name, extension: extension ?? '' } } -/** - * Network error class. - */ +/** Network error class. */ export class NetworkError extends Error { /** * Create a new instance of the {@link NetworkError} class. @@ -1336,9 +1376,7 @@ export class NetworkError extends Error { super(message) } } -/** - * Error class for when the user is not authorized to access a resource. - */ +/** Error class for when the user is not authorized to access a resource. */ export class NotAuthorizedError extends NetworkError {} // =============== diff --git a/app/common/src/utilities/data/array.ts b/app/common/src/utilities/data/array.ts index b225b58ce6b..392f7cdc37c 100644 --- a/app/common/src/utilities/data/array.ts +++ b/app/common/src/utilities/data/array.ts @@ -15,15 +15,19 @@ export function shallowEqual(a: readonly T[], b: readonly T[]) { // === includes === // ================ -/** Returns a type predicate that returns true if and only if the value is in the array. - * The array MUST contain every element of `T`. */ +/** + * Returns a type predicate that returns true if and only if the value is in the array. + * The array MUST contain every element of `T`. + */ export function includes(array: T[], item: unknown): item is T { const arrayOfUnknown: unknown[] = array return arrayOfUnknown.includes(item) } -/** Returns a type predicate that returns true if and only if the value is in the iterable. - * The iterable MUST contain every element of `T`. */ +/** + * Returns a type predicate that returns true if and only if the value is in the iterable. + * The iterable MUST contain every element of `T`. + */ export function includesPredicate(array: Iterable) { const set: Set = array instanceof Set ? array : new Set(array) return (item: unknown): item is T => set.has(item) @@ -36,32 +40,40 @@ export function includesPredicate(array: Iterable) { /** The value returned when {@link Array.findIndex} fails. */ const NOT_FOUND = -1 -/** Insert items before the first index `i` for which `predicate(array[i])` is `true`. - * Insert the items at the end if the `predicate` never returns `true`. */ +/** + * Insert items before the first index `i` for which `predicate(array[i])` is `true`. + * Insert the items at the end if the `predicate` never returns `true`. + */ export function spliceBefore(array: T[], items: T[], predicate: (value: T) => boolean) { const index = array.findIndex(predicate) array.splice(index === NOT_FOUND ? array.length : index, 0, ...items) return array } -/** Return a copy of the array, with items inserted before the first index `i` for which +/** + * Return a copy of the array, with items inserted before the first index `i` for which * `predicate(array[i])` is `true`. The items are inserted at the end if the `predicate` never - * returns `true`. */ + * returns `true`. + */ export function splicedBefore(array: T[], items: T[], predicate: (value: T) => boolean) { return spliceBefore(Array.from(array), items, predicate) } -/** Insert items after the first index `i` for which `predicate(array[i])` is `true`. - * Insert the items at the end if the `predicate` never returns `true`. */ +/** + * Insert items after the first index `i` for which `predicate(array[i])` is `true`. + * Insert the items at the end if the `predicate` never returns `true`. + */ export function spliceAfter(array: T[], items: T[], predicate: (value: T) => boolean) { const index = array.findIndex(predicate) array.splice(index === NOT_FOUND ? array.length : index + 1, 0, ...items) return array } -/** Return a copy of the array, with items inserted after the first index `i` for which +/** + * Return a copy of the array, with items inserted after the first index `i` for which * `predicate(array[i])` is `true`. The items are inserted at the end if the `predicate` never - * returns `true`. */ + * returns `true`. + */ export function splicedAfter(array: T[], items: T[], predicate: (value: T) => boolean) { return spliceAfter(Array.from(array), items, predicate) } diff --git a/app/common/src/utilities/data/dateTime.ts b/app/common/src/utilities/data/dateTime.ts index 2ac9f84e9c9..1a009569ae8 100644 --- a/app/common/src/utilities/data/dateTime.ts +++ b/app/common/src/utilities/data/dateTime.ts @@ -35,8 +35,10 @@ export type Rfc3339DateTime = newtype.Newtype // eslint-disable-next-line @typescript-eslint/no-redeclare export const Rfc3339DateTime = newtype.newtypeConstructor() -/** Return a new {@link Date} with units below days (hours, minutes, seconds and milliseconds) - * set to `0`. */ +/** + * Return a new {@link Date} with units below days (hours, minutes, seconds and milliseconds) + * set to `0`. + */ export function toDate(dateTime: Date) { return new Date(dateTime.getFullYear(), dateTime.getMonth(), dateTime.getDate()) } diff --git a/app/common/src/utilities/data/newtype.ts b/app/common/src/utilities/data/newtype.ts index 9ecb616fd57..b6add8ced74 100644 --- a/app/common/src/utilities/data/newtype.ts +++ b/app/common/src/utilities/data/newtype.ts @@ -12,8 +12,10 @@ type NewtypeVariant = { readonly _$type: TypeName } -/** An interface specifying the variant of a newtype, where the discriminator is mutable. - * This is safe, as the discriminator should be a string literal type anyway. */ +/** + * An interface specifying the variant of a newtype, where the discriminator is mutable. + * This is safe, as the discriminator should be a string literal type anyway. + */ // This is required for compatibility with the dependency `enso-chat`. // eslint-disable-next-line no-restricted-syntax type MutableNewtypeVariant = { @@ -21,7 +23,8 @@ type MutableNewtypeVariant = { _$type: TypeName } -/** Used to create a "branded type", +/** + * Used to create a "branded type", * which contains a property that only exists at compile time. * * `Newtype` and `Newtype` are not compatible with each other, @@ -33,11 +36,14 @@ type MutableNewtypeVariant = { * It is similar to a `newtype` in other languages. * Note however because TypeScript is structurally typed, * a branded type is assignable to its base type: - * `a: string = asNewtype>(b)` successfully typechecks. */ + * `a: string = asNewtype>(b)` successfully typechecks. + */ export type Newtype = NewtypeVariant & T -/** Extracts the original type out of a {@link Newtype}. - * Its only use is in {@link newtypeConstructor}. */ +/** + * Extracts the original type out of a {@link Newtype}. + * Its only use is in {@link newtypeConstructor}. + */ type UnNewtype> = T extends infer U & NewtypeVariant ? U extends infer V & MutableNewtypeVariant ? @@ -51,9 +57,11 @@ type NotNewtype = { readonly _$type?: never } -/** Converts a value that is not a newtype, to a value that is a newtype. +/** + * Converts a value that is not a newtype, to a value that is a newtype. * This function intentionally returns another function, to ensure that each function instance - * is only used for one type, avoiding the de-optimization caused by polymorphic functions. */ + * is only used for one type, avoiding the de-optimization caused by polymorphic functions. + */ export function newtypeConstructor>() { // This cast is unsafe. // `T` has an extra property `_$type` which is used purely for typechecking diff --git a/app/common/src/utilities/data/object.ts b/app/common/src/utilities/data/object.ts index 1e7e7a2a107..10dd6186f92 100644 --- a/app/common/src/utilities/data/object.ts +++ b/app/common/src/utilities/data/object.ts @@ -16,8 +16,10 @@ export type Mutable = { /** Prevents generic parameter inference by hiding the type parameter behind a conditional type. */ type NoInfer = [T][T extends T ? 0 : never] -/** Immutably shallowly merge an object with a partial update. - * Does not preserve classes. Useful for preserving order of properties. */ +/** + * Immutably shallowly merge an object with a partial update. + * Does not preserve classes. Useful for preserving order of properties. + */ export function merge(object: T, update: Partial): T { for (const [key, value] of Object.entries(update)) { // eslint-disable-next-line no-restricted-syntax @@ -57,8 +59,10 @@ export function unsafeMutable(object: T): { -readonly [K in ke // === unsafeEntries === // ===================== -/** Return the entries of an object. UNSAFE only when it is possible for an object to have - * extra keys. */ +/** + * Return the entries of an object. UNSAFE only when it is possible for an object to have + * extra keys. + */ export function unsafeEntries( object: T, ): readonly { [K in keyof T]: readonly [K, T[K]] }[keyof T][] { @@ -83,8 +87,10 @@ export function unsafeRemoveUndefined( // === mapEntries === // ================== -/** Return the entries of an object. UNSAFE only when it is possible for an object to have - * extra keys. */ +/** + * Return the entries of an object. UNSAFE only when it is possible for an object to have + * extra keys. + */ export function mapEntries( object: Record, map: (key: K, value: V) => W, diff --git a/app/common/src/utilities/permissions.ts b/app/common/src/utilities/permissions.ts index d9bc2aa3def..7404691de58 100644 --- a/app/common/src/utilities/permissions.ts +++ b/app/common/src/utilities/permissions.ts @@ -111,8 +111,10 @@ export const FROM_PERMISSION_ACTION: Readonly> = { [Permission.owner]: PermissionAction.own, [Permission.admin]: PermissionAction.admin, @@ -123,8 +125,10 @@ export const TYPE_TO_PERMISSION_ACTION: Readonly> = { [Permission.owner]: 'ownerPermissionType', [Permission.admin]: 'adminPermissionType', @@ -181,13 +185,13 @@ interface BasePermissions { } /** Owner permissions for an asset. */ -interface OwnerPermissions extends BasePermissions {} +type OwnerPermissions = BasePermissions /** Admin permissions for an asset. */ -interface AdminPermissions extends BasePermissions {} +type AdminPermissions = BasePermissions /** Editor permissions for an asset. */ -interface EditPermissions extends BasePermissions {} +type EditPermissions = BasePermissions /** Reader permissions for an asset. */ interface ReadPermissions extends BasePermissions { diff --git a/app/common/src/utilities/style/__tests__/tabBar.test.ts b/app/common/src/utilities/style/__tests__/tabBar.test.ts index 75101edc763..205896c5b78 100644 --- a/app/common/src/utilities/style/__tests__/tabBar.test.ts +++ b/app/common/src/utilities/style/__tests__/tabBar.test.ts @@ -42,7 +42,7 @@ const guiTabCases = [ v.test.each([ { group: 'Dashboard', cases: dashboardTabCases }, { group: 'GUI', cases: guiTabCases }, -])('Tab clip path: $group', ({ group, cases }) => { +])('Tab clip path: $group', ({ cases }) => { cases.forEach(({ input, expected }) => { const result = tabBar.tabClipPath(input.bounds, input.radius, (input as TabClipPathInput)?.side) v.expect(result).toBe(expected) diff --git a/app/common/tsconfig.json b/app/common/tsconfig.json index 4db43171dfe..4837545253f 100644 --- a/app/common/tsconfig.json +++ b/app/common/tsconfig.json @@ -2,8 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "lib": ["DOM", "es2023"], - "allowJs": false, - "checkJs": false, + "allowJs": true, + "checkJs": true, "skipLibCheck": false }, "include": ["./src/", "./src/text/english.json", "../types/"] diff --git a/app/gui/e2e/dashboard/actions/EditorPageActions.ts b/app/gui/e2e/dashboard/actions/EditorPageActions.ts index 6121f2c93ec..4df9a30fb59 100644 --- a/app/gui/e2e/dashboard/actions/EditorPageActions.ts +++ b/app/gui/e2e/dashboard/actions/EditorPageActions.ts @@ -12,9 +12,7 @@ export default class EditorPageActions extends PageActions { get goToPage(): Omit { return goToPageActions.goToPageActions(this.step.bind(this)) } - /** - * Waits for the editor to load. - */ + /** Waits for the editor to load. */ waitForEditorToLoad(): EditorPageActions { return this.step('wait for the editor to load', async () => { await this.page.waitForSelector('[data-testid=editor]', { state: 'visible' }) diff --git a/app/gui/e2e/project-view/edgeInteractions.spec.ts b/app/gui/e2e/project-view/edgeInteractions.spec.ts index 87bc1cdfdf8..b04ecec1468 100644 --- a/app/gui/e2e/project-view/edgeInteractions.spec.ts +++ b/app/gui/e2e/project-view/edgeInteractions.spec.ts @@ -35,9 +35,7 @@ test('Disconnect an edge from a port', async ({ page }) => { await expect(await edgesToNodeWithBinding(page, 'sum')).toHaveCount(EDGE_PARTS) }) -/** - * Scenario: We replace the `sum` parameter in the `prod` node` with the `ten` node. - */ +/** Scenario: We replace the `sum` parameter in the `prod` node` with the `ten` node. */ test('Connect an node to a port', async ({ page }) => { await initGraph(page) @@ -57,9 +55,7 @@ test('Connect an node to a port', async ({ page }) => { await expect(graphNodeByBinding(page, 'prod')).toContainText('ten') }) -/** - * As above, but by dragging edge instead of clicking source and target separately. - */ +/** As above, but by dragging edge instead of clicking source and target separately. */ test('Connect an node to a port via dragging the edge', async ({ page }) => { await initGraph(page) diff --git a/app/gui/e2e/project-view/edgeRendering.spec.ts b/app/gui/e2e/project-view/edgeRendering.spec.ts index 32e99bff05b..f1de01f3dc5 100644 --- a/app/gui/e2e/project-view/edgeRendering.spec.ts +++ b/app/gui/e2e/project-view/edgeRendering.spec.ts @@ -34,9 +34,7 @@ test('Existence of edges between nodes', async ({ page }) => { await expect(await edgesToNodeWithBinding(page, 'five')).toHaveCount(0) }) -/** - * Prepare the graph for the tests. We drag the `ten` node to the right for better access to its outgoing edge. - */ +/** Prepare the graph for the tests. We drag the `ten` node to the right for better access to its outgoing edge. */ async function initGraph(page: Page) { await actions.goToGraph(page) await actions.dragNodeByBinding(page, 'ten', 400, 0) diff --git a/app/gui/e2e/project-view/tableVisualisation.spec.ts b/app/gui/e2e/project-view/tableVisualisation.spec.ts index 03ba165d91d..168ff37cc99 100644 --- a/app/gui/e2e/project-view/tableVisualisation.spec.ts +++ b/app/gui/e2e/project-view/tableVisualisation.spec.ts @@ -5,9 +5,7 @@ import { mockExpressionUpdate } from './expressionUpdates' import * as locate from './locate' import { graphNodeByBinding } from './locate' -/** - * Prepare the graph for the tests. We add the table type to the `aggregated` node. - */ +/** Prepare the graph for the tests. We add the table type to the `aggregated` node. */ async function initGraph(page: Page) { await actions.goToGraph(page) await mockExpressionUpdate(page, 'aggregated', { type: 'Standard.Table.Table.Table' }) diff --git a/app/gui/package.json b/app/gui/package.json index 2400590773a..29543619ca7 100644 --- a/app/gui/package.json +++ b/app/gui/package.json @@ -21,7 +21,7 @@ "build": "vite build", "build-cloud": "cross-env CLOUD_BUILD=true corepack pnpm run build", "preview": "vite preview", - "lint": "cross-env eslint . --max-warnings=0", + "lint": "eslint . --max-warnings=0", "format": "prettier --version && prettier --write src/ && eslint . --fix", "dev:vite": "vite", "test": "corepack pnpm run /^^^^test:.*/", diff --git a/app/gui/project-manager-shim-middleware/desktopEnvironment.ts b/app/gui/project-manager-shim-middleware/desktopEnvironment.ts index dd29a004609..48fb34546de 100644 --- a/app/gui/project-manager-shim-middleware/desktopEnvironment.ts +++ b/app/gui/project-manager-shim-middleware/desktopEnvironment.ts @@ -7,9 +7,7 @@ export const DOCUMENTS = getDocumentsPath() const CHILD_PROCESS_TIMEOUT = 3000 -/** - * Detects path of the user documents directory depending on the operating system. - */ +/** Detects path of the user documents directory depending on the operating system. */ function getDocumentsPath(): string | undefined { if (process.platform === 'linux') { return getLinuxDocumentsPath() @@ -22,18 +20,14 @@ function getDocumentsPath(): string | undefined { } } -/** - * Returns the user documents path on Linux. - */ +/** Returns the user documents path on Linux. */ function getLinuxDocumentsPath(): string { const xdgDocumentsPath = getXdgDocumentsPath() return xdgDocumentsPath ?? path.join(os.homedir(), 'enso') } -/** - * Gets the documents directory from the XDG directory management system. - */ +/** Gets the documents directory from the XDG directory management system. */ function getXdgDocumentsPath(): string | undefined { const out = childProcess.spawnSync('xdg-user-dir', ['DOCUMENTS'], { timeout: CHILD_PROCESS_TIMEOUT, @@ -54,9 +48,7 @@ function getMacOsDocumentsPath(): string { return path.join(os.homedir(), 'Documents') } -/** - * Get the path to the `My Documents` Windows directory. - */ +/** Get the path to the `My Documents` Windows directory. */ function getWindowsDocumentsPath(): string | undefined { const out = childProcess.spawnSync( 'reg', diff --git a/app/gui/src/dashboard/appUtils.tsx b/app/gui/src/dashboard/appUtils.tsx index 08f1fd9ae63..593f02ca64b 100644 --- a/app/gui/src/dashboard/appUtils.tsx +++ b/app/gui/src/dashboard/appUtils.tsx @@ -45,23 +45,17 @@ export const SUPPORT_EMAIL = 'cloud@enso.org' /** Return the `mailto:` URL for contacting support. */ export const SUPPORT_EMAIL_URL = `mailto:${SUPPORT_EMAIL}` -/** - * Build a Subscription URL for a given plan. - */ +/** Build a Subscription URL for a given plan. */ export function getUpgradeURL(plan: string): string { return SUBSCRIBE_PATH + '?plan=' + plan } -/** - * Return the mailto URL for contacting sales. - */ +/** Return the mailto URL for contacting sales. */ export function getSalesEmail(): string { return 'mailto:contact@enso.org' } -/** - * Build a Subscription URL for contacting sales. - */ +/** Build a Subscription URL for contacting sales. */ export function getContactSalesURL(): string { return 'mailto:contact@enso.org?subject=Upgrading%20to%20Organization%20Plan' } diff --git a/app/gui/src/dashboard/authentication/cognito.mock.ts b/app/gui/src/dashboard/authentication/cognito.mock.ts index 91aea011d4c..76c2da1aef2 100644 --- a/app/gui/src/dashboard/authentication/cognito.mock.ts +++ b/app/gui/src/dashboard/authentication/cognito.mock.ts @@ -306,16 +306,12 @@ export class Cognito { } } - /** - * Refresh the current user's session. - */ + /** Refresh the current user's session. */ async refreshUserSession() { return Promise.resolve(results.Ok(null)) } - /** - * Returns MFA preference for the current user. - */ + /** Returns MFA preference for the current user. */ async getMFAPreference() { return Promise.resolve(results.Ok('NOMFA')) } diff --git a/app/gui/src/dashboard/authentication/cognito.ts b/app/gui/src/dashboard/authentication/cognito.ts index 0983b88507f..255723a3c97 100644 --- a/app/gui/src/dashboard/authentication/cognito.ts +++ b/app/gui/src/dashboard/authentication/cognito.ts @@ -74,9 +74,7 @@ interface UserAttributes { } /* eslint-enable @typescript-eslint/naming-convention */ -/** - * The type of multi-factor authentication (MFA) that the user has set up. - */ +/** The type of multi-factor authentication (MFA) that the user has set up. */ export type MfaType = 'NOMFA' | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' | 'TOTP' /** @@ -244,9 +242,7 @@ export class Cognito { return userInfo.attributes['custom:organizationId'] ?? null } - /** - * Gets user email from cognito - */ + /** Gets user email from cognito */ async email() { // This `any` comes from a third-party API and cannot be avoided. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment @@ -337,9 +333,7 @@ export class Cognito { return result.mapErr(intoAmplifyErrorOrThrow).mapErr(intoSignInWithPasswordErrorOrThrow) } - /** - * Refresh the current user session. - */ + /** Refresh the current user session. */ async refreshUserSession() { const result = await results.Result.wrapAsync(async () => { const currentUser = await currentAuthenticatedUser() @@ -432,9 +426,7 @@ export class Cognito { } } - /** - * Start the TOTP setup process. Returns the secret and the URL to scan the QR code. - */ + /** Start the TOTP setup process. Returns the secret and the URL to scan the QR code. */ async setupTOTP() { const email = await this.email() const cognitoUserResult = await currentAuthenticatedUser() @@ -472,9 +464,7 @@ export class Cognito { } } - /** - * Set the user's preferred MFA method. - */ + /** Set the user's preferred MFA method. */ async updateMFAPreference(mfaMethod: MfaType) { const cognitoUserResult = await currentAuthenticatedUser() if (cognitoUserResult.ok) { @@ -488,9 +478,7 @@ export class Cognito { } } - /** - * Get the user's preferred MFA method. - */ + /** Get the user's preferred MFA method. */ async getMFAPreference() { const cognitoUserResult = await currentAuthenticatedUser() if (cognitoUserResult.ok) { @@ -523,9 +511,7 @@ export class Cognito { } } - /** - * Confirm the sign in with the MFA token. - */ + /** Confirm the sign in with the MFA token. */ async confirmSignIn( user: amplify.CognitoUser, confirmationCode: string, diff --git a/app/gui/src/dashboard/components/AnimatedBackground.tsx b/app/gui/src/dashboard/components/AnimatedBackground.tsx index 93bb04f9b94..e48b7a45e63 100644 --- a/app/gui/src/dashboard/components/AnimatedBackground.tsx +++ b/app/gui/src/dashboard/components/AnimatedBackground.tsx @@ -11,9 +11,7 @@ import { createContext, useContext, useId } from 'react' import { twJoin } from '#/utilities/tailwindMerge' import invariant from 'tiny-invariant' -/** - * Props for {@link AnimatedBackground}. - */ +/** Props for {@link AnimatedBackground}. */ interface AnimatedBackgroundProps extends PropsWithChildren { readonly value: string readonly transition?: Transition @@ -37,9 +35,7 @@ const DEFAULT_TRANSITION: Transition = { velocity: 12, } -/** - * `` component visually highlights selected items by sliding a background into view when hovered over or clicked. - */ +/** `` component visually highlights selected items by sliding a background into view when hovered over or clicked. */ export function AnimatedBackground(props: AnimatedBackgroundProps) { const { value, transition = DEFAULT_TRANSITION, children } = props const layoutId = useId() @@ -51,18 +47,14 @@ export function AnimatedBackground(props: AnimatedBackgroundProps) { ) } -/** - * Props for {@link AnimatedBackground.Item}. - */ +/** Props for {@link AnimatedBackground.Item}. */ interface AnimatedBackgroundItemProps extends PropsWithChildren { readonly value: string readonly className?: string readonly animationClassName?: string } -/** - * Item within an {@link AnimatedBackground}. - */ +/** Item within an {@link AnimatedBackground}. */ AnimatedBackground.Item = function AnimatedBackgroundItem(props: AnimatedBackgroundItemProps) { const context = useContext(AnimatedBackgroundContext) invariant(context, 'useAnimatedBackground must be used within an AnimatedBackgroundProvider') diff --git a/app/gui/src/dashboard/components/AriaComponents/Alert/Alert.tsx b/app/gui/src/dashboard/components/AriaComponents/Alert/Alert.tsx index 40c06b17365..ee15e32e8a9 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Alert/Alert.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Alert/Alert.tsx @@ -61,9 +61,7 @@ export interface AlertProps extends PropsWithChildren, VariantProps, HTMLAttributes { - /** - * The icon to display in the Alert - */ + /** The icon to display in the Alert */ readonly icon?: React.ReactElement | string | null | undefined } diff --git a/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx b/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx index 0b387579b4d..3b5b1da99fc 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx @@ -22,39 +22,29 @@ export type ButtonProps = | (BaseButtonProps & Omit & PropsWithoutHref) | (BaseButtonProps & Omit & PropsWithHref) -/** - * Props for a button with an href. - */ +/** Props for a button with an href. */ interface PropsWithHref { readonly href: string } -/** - * Props for a button without an href. - */ +/** Props for a button without an href. */ interface PropsWithoutHref { readonly href?: never } -/** - * Base props for a button. - */ +/** Base props for a button. */ export interface BaseButtonProps extends Omit, 'iconOnly'> { /** Falls back to `aria-label`. Pass `false` to explicitly disable the tooltip. */ readonly tooltip?: React.ReactElement | string | false | null readonly tooltipPlacement?: aria.Placement - /** - * The icon to display in the button - */ + /** The icon to display in the button */ readonly icon?: | React.ReactElement | string | ((render: Render) => React.ReactElement | string | null) | null - /** - * When `true`, icon will be shown only when hovered. - */ + /** When `true`, icon will be shown only when hovered. */ readonly showIconOnHover?: boolean /** * Handler that is called when the press is released over the target. diff --git a/app/gui/src/dashboard/components/AriaComponents/Checkbox/Checkbox.tsx b/app/gui/src/dashboard/components/AriaComponents/Checkbox/Checkbox.tsx index 399d0aa019e..f89983b253b 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Checkbox/Checkbox.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Checkbox/Checkbox.tsx @@ -33,9 +33,7 @@ import type { TestIdProps } from '../types' import { useCheckboxContext } from './CheckboxContext' import { CheckboxGroup } from './CheckboxGroup' -/** - * Props for the {@link Checkbox} component. - */ +/** Props for the {@link Checkbox} component. */ export type CheckboxProps> = Omit< VariantProps, 'isDisabled' | 'isInvalid' @@ -46,18 +44,14 @@ export type CheckboxProps } & (CheckboxGroupCheckboxProps | StandaloneCheckboxProps) -/** - * Props for the {@link Checkbox} component when used inside a {@link CheckboxGroup}. - */ +/** Props for the {@link Checkbox} component when used inside a {@link CheckboxGroup}. */ interface CheckboxGroupCheckboxProps extends AriaCheckboxProps { readonly value: string readonly form?: never readonly name?: never } -/** - * Props for the {@link Checkbox} component when used outside of a {@link CheckboxGroup}. - */ +/** Props for the {@link Checkbox} component when used outside of a {@link CheckboxGroup}. */ type StandaloneCheckboxProps< Schema extends TSchema, TFieldName extends FieldPath, @@ -123,9 +117,7 @@ export const TICK_VARIANTS: Variants = { }, } -/** - * Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected. - */ +/** Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected. */ // eslint-disable-next-line no-restricted-syntax export const Checkbox = forwardRef(function Checkbox< Schema extends TSchema, diff --git a/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxContext.tsx b/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxContext.tsx index fa8ecd099b8..13370f58410 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxContext.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxContext.tsx @@ -1,6 +1,4 @@ -/** - * @file - */ +/** @file */ import { useEventCallback } from '#/hooks/eventCallbackHooks' import type { PropsWithChildren } from 'react' import { createContext, useContext, useMemo, useState } from 'react' @@ -8,9 +6,7 @@ import type { StoreApi } from 'zustand' import { createStore } from 'zustand' import type { TSchema, UseFormRegisterReturn } from '../Form' -/** - * Context for the checkbox. - */ +/** Context for the checkbox. */ interface CheckboxContextType { readonly store: StoreApi readonly addSelected: (selected: string) => void @@ -25,9 +21,7 @@ const CheckboxContext = createContext({ toggleSelected: () => {}, }) -/** - * Gets the context for the checkbox. - */ +/** Gets the context for the checkbox. */ export function useCheckboxContext() { return useContext(CheckboxContext) } @@ -50,9 +44,7 @@ export function useCheckboxGroupState() { */ type CheckGroupPropsState = CheckBoxGroupPropsStateInsideGroup | CheckBoxGroupPropsStateOutsideGroup -/** - * Checkbox group state when the checkbox is inside a group. - */ +/** Checkbox group state when the checkbox is inside a group. */ interface CheckBoxGroupPropsStateInsideGroup { readonly insideGroup: true readonly selected: Set @@ -60,16 +52,12 @@ interface CheckBoxGroupPropsStateInsideGroup { readonly field: UseFormRegisterReturn } -/** - * Checkbox group state when the checkbox is not inside a group. - */ +/** Checkbox group state when the checkbox is not inside a group. */ interface CheckBoxGroupPropsStateOutsideGroup { readonly insideGroup: false } -/** - * Props for {@link CheckboxGroupProvider}. - */ +/** Props for {@link CheckboxGroupProvider}. */ export interface CheckboxGroupProviderProps extends PropsWithChildren { readonly name: string readonly onChange: (selected: string[]) => void @@ -77,9 +65,7 @@ export interface CheckboxGroupProviderProps extends PropsWithChildren { readonly defaultValue?: string[] | undefined } -/** - * Checkbox group provider used to manage the state of a group of checkboxes. - */ +/** Checkbox group provider used to manage the state of a group of checkboxes. */ export function CheckboxGroupProvider(props: CheckboxGroupProviderProps) { const { children, onChange, name, field, defaultValue = [] } = props diff --git a/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxGroup.tsx b/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxGroup.tsx index 787c8a32db0..ff55cbc7718 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxGroup.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Checkbox/CheckboxGroup.tsx @@ -16,9 +16,7 @@ import { Form, type FieldPath, type FieldProps, type FieldStateProps, type TSche import type { TestIdProps } from '../types' import { CheckboxGroupProvider } from './CheckboxContext' -/** - * Props for the {@link CheckboxGroupProps} component. - */ +/** Props for the {@link CheckboxGroupProps} component. */ export interface CheckboxGroupProps> extends FieldStateProps, FieldProps, @@ -36,9 +34,7 @@ const CHECKBOX_GROUP_STYLES = tv({ variants: { fullWidth: { true: 'w-full' } }, }) -/** - * A CheckboxGroup allows users to select one or more items from a list of choices. - */ +/** A CheckboxGroup allows users to select one or more items from a list of choices. */ // eslint-disable-next-line no-restricted-syntax export const CheckboxGroup = forwardRef( >( diff --git a/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx b/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx index fa22be96377..c4567a91f45 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx @@ -12,14 +12,10 @@ import * as eventCallback from '#/hooks/eventCallbackHooks' import * as button from '../Button' import * as dialogProvider from './DialogProvider' -/** - * Props for {@link Close} component. - */ +/** Props for {@link Close} component. */ export type CloseProps = button.ButtonProps -/** - * Close button for a dialog. - */ +/** Close button for a dialog. */ export function Close(props: CloseProps) { const dialogContext = dialogProvider.useDialogContext() diff --git a/app/gui/src/dashboard/components/AriaComponents/Dialog/Dialog.tsx b/app/gui/src/dashboard/components/AriaComponents/Dialog/Dialog.tsx index b55489d0471..09a62cda88c 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Dialog/Dialog.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Dialog/Dialog.tsx @@ -23,9 +23,7 @@ import { DIALOG_BACKGROUND } from './variants' // ================= // === Constants === // ================= -/** - * Props for the {@link Dialog} component. - */ +/** Props for the {@link Dialog} component. */ export interface DialogProps extends types.DialogProps, Omit, 'scrolledToTop'> {} @@ -177,9 +175,7 @@ export function Dialog(props: DialogProps) { const [isScrolledToTop, setIsScrolledToTop] = React.useState(true) - /** - * Handles the scroll event on the dialog content. - */ + /** Handles the scroll event on the dialog content. */ const handleScroll = (scrollTop: number) => { React.startTransition(() => { if (scrollTop > 0) { diff --git a/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogProvider.tsx b/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogProvider.tsx index f80c8580ffc..a77f206d297 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogProvider.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogProvider.tsx @@ -5,28 +5,20 @@ */ import * as React from 'react' -/** - * The context value for a dialog. - */ +/** The context value for a dialog. */ export interface DialogContextValue { readonly close: () => void readonly dialogId: string } -/** - * The context for a dialog. - */ +/** The context for a dialog. */ const DialogContext = React.createContext(null) -/** - * The provider for a dialog. - */ +/** The provider for a dialog. */ // eslint-disable-next-line no-restricted-syntax export const DialogProvider = DialogContext.Provider -/** - * Custom hook to get the dialog context. - */ +/** Custom hook to get the dialog context. */ export function useDialogContext() { return React.useContext(DialogContext) } diff --git a/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogStackProvider.tsx b/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogStackProvider.tsx index 60cf421bd3f..90ad796d787 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogStackProvider.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Dialog/DialogStackProvider.tsx @@ -1,6 +1,4 @@ -/** - * @file This file provides the DialogStackProvider component and related functionality. - */ +/** @file This file provides the DialogStackProvider component and related functionality. */ import * as React from 'react' @@ -8,17 +6,13 @@ import invariant from 'tiny-invariant' import * as eventCallbackHooks from '#/hooks/eventCallbackHooks' -/** - * DialogStackItem represents an item in the dialog stack. - */ +/** DialogStackItem represents an item in the dialog stack. */ export interface DialogStackItem { readonly id: string readonly type: 'dialog-fullscreen' | 'dialog' | 'popover' } -/** - * DialogStackContextType represents the context for the dialog stack. - */ +/** DialogStackContextType represents the context for the dialog stack. */ export interface DialogStackContextType { readonly stack: DialogStackItem[] readonly dialogsStack: DialogStackItem[] @@ -28,9 +22,7 @@ export interface DialogStackContextType { const DialogStackContext = React.createContext(null) -/** - * DialogStackProvider is a React component that provides the dialog stack context to its children. - */ +/** DialogStackProvider is a React component that provides the dialog stack context to its children. */ export function DialogStackProvider(props: React.PropsWithChildren) { const { children } = props @@ -72,9 +64,7 @@ updated properly.`) return {children} } -/** - * DialogStackRegistrar is a React component that registers a dialog in the dialog stack. - */ +/** DialogStackRegistrar is a React component that registers a dialog in the dialog stack. */ export function DialogStackRegistrar(props: React.PropsWithChildren) { const { children, id: idRaw, type: typeRaw } = props const idRef = React.useRef(idRaw) @@ -100,16 +90,12 @@ export function DialogStackRegistrar(props: React.PropsWithChildren -/** - * Props passed to the render function of a {@link DialogTrigger}. - */ +/** Props passed to the render function of a {@link DialogTrigger}. */ export interface DialogTriggerRenderProps { readonly isOpen: boolean readonly close: () => void readonly open: () => void } -/** - * Props for a {@link DialogTrigger}. - */ +/** Props for a {@link DialogTrigger}. */ export interface DialogTriggerProps extends Omit { - /** - * The trigger element. - */ + /** The trigger element. */ readonly children: [ React.ReactElement, React.ReactElement | ((props: DialogTriggerRenderProps) => React.ReactElement), diff --git a/app/gui/src/dashboard/components/AriaComponents/Dialog/utilities.ts b/app/gui/src/dashboard/components/AriaComponents/Dialog/utilities.ts index 52e966e8621..295ccaca184 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Dialog/utilities.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Dialog/utilities.ts @@ -23,16 +23,12 @@ const IGNORE_INTERACT_OUTSIDE_ELEMENTS = [ const IGNORE_INTERACT_OUTSIDE_ELEMENTS_SELECTOR = `:is(${IGNORE_INTERACT_OUTSIDE_ELEMENTS.join(', ')})` -/** - * Check if the element is a part of a component that should ignore the interact outside event - */ +/** Check if the element is a part of a component that should ignore the interact outside event */ export function shouldIgnoreInteractOutside(element: HTMLElement) { return element.closest(IGNORE_INTERACT_OUTSIDE_ELEMENTS_SELECTOR) } -/** - * Props for {@link useInteractOutside} - */ +/** Props for {@link useInteractOutside} */ export interface UseInteractOutsideProps { readonly ref: React.RefObject readonly id: string @@ -40,9 +36,7 @@ export interface UseInteractOutsideProps { readonly isDisabled?: boolean } -/** - * Hook that handles the interact outside event for the dialog - */ +/** Hook that handles the interact outside event for the dialog */ export function useInteractOutside(props: UseInteractOutsideProps) { const { ref, id, onInteractOutside, isDisabled = false } = props const shouldCloseOnInteractOutsideRef = React.useRef(false) diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/Field.tsx b/app/gui/src/dashboard/components/AriaComponents/Form/components/Field.tsx index f2a52394d47..d3d52bcf901 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/Field.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/Field.tsx @@ -14,9 +14,7 @@ import * as text from '../../Text' import { Form } from '../Form' import type * as types from './types' -/** - * Props for Field component - */ +/** Props for Field component */ export interface FieldComponentProps extends VariantProps, types.FieldProps { @@ -29,16 +27,12 @@ export interface FieldComponentProps readonly style?: React.CSSProperties | undefined } -/** - * Props for Field variants - */ +/** Props for Field variants */ export interface FieldVariantProps { readonly fieldVariants?: VariantProps['variants'] | undefined } -/** - * Props for Field children - */ +/** Props for Field children */ export interface FieldChildrenRenderProps { readonly isInvalid: boolean readonly isDirty: boolean @@ -65,9 +59,7 @@ export const FIELD_STYLES = tv({ defaultVariants: { fullWidth: true }, }) -/** - * Field component - */ +/** Field component */ // eslint-disable-next-line no-restricted-syntax export const Field = forwardRef(function Field( props: FieldComponentProps, diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/FormError.tsx b/app/gui/src/dashboard/components/AriaComponents/Form/components/FormError.tsx index 9bc17196403..c1f1f32c191 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/FormError.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/FormError.tsx @@ -13,18 +13,14 @@ import * as reactAriaComponents from '#/components/AriaComponents' import * as formContext from './FormProvider' import type * as types from './types' -/** - * Props for the FormError component. - */ +/** Props for the FormError component. */ export interface FormErrorProps extends Omit { // We do not need to know the form fields. // eslint-disable-next-line @typescript-eslint/no-explicit-any readonly form?: types.FormInstance } -/** - * Form error component. - */ +/** Form error component. */ export function FormError(props: FormErrorProps) { const { size = 'large', variant = 'error', rounded = 'large', ...alertProps } = props @@ -33,9 +29,7 @@ export function FormError(props: FormErrorProps) { const { errors } = formState const { getText } = textProvider.useText() - /** - * Get the error message. - */ + /** Get the error message. */ const getSubmitError = (): string | null => { const formErrors = errors.root diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/FormProvider.tsx b/app/gui/src/dashboard/components/AriaComponents/Form/components/FormProvider.tsx index baa9679aed0..af276bb20f9 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/FormProvider.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/FormProvider.tsx @@ -9,9 +9,7 @@ import invariant from 'tiny-invariant' import type * as types from './types' import type { FormInstance, FormInstanceValidated } from './types' -/** - * Context type for the form provider. - */ +/** Context type for the form provider. */ interface FormContextType { readonly form: types.UseFormReturn } @@ -20,9 +18,7 @@ interface FormContextType { // eslint-disable-next-line @typescript-eslint/no-explicit-any const FormContext = createContext | null>(null) -/** - * Provides the form instance to the component tree. - */ +/** Provides the form instance to the component tree. */ export function FormProvider( props: FormContextType & PropsWithChildren, ) { @@ -36,9 +32,7 @@ export function FormProvider( ) } -/** - * Returns the form instance from the context. - */ +/** Returns the form instance from the context. */ export function useFormContext( form?: FormInstanceValidated, ): FormInstance { @@ -56,9 +50,7 @@ export function useFormContext( } } -/** - * Returns the form instance from the context, or null if the context is not available. - */ +/** Returns the form instance from the context, or null if the context is not available. */ export function useOptionalFormContext< Form extends FormInstanceValidated | undefined, Schema extends types.TSchema, diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/Reset.tsx b/app/gui/src/dashboard/components/AriaComponents/Form/components/Reset.tsx index 9224748b912..cca63f8a6b1 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/Reset.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/Reset.tsx @@ -11,9 +11,7 @@ import { useText } from '#/providers/TextProvider' import * as formContext from './FormProvider' import type * as types from './types' -/** - * Props for the Reset component. - */ +/** Props for the Reset component. */ export interface ResetProps extends Omit { /** * Connects the submit button to a form. @@ -28,9 +26,7 @@ export interface ResetProps extends Omit readonly action?: 'cancel' | 'reset' } -/** - * Reset button for forms. - */ +/** Reset button for forms. */ export function Reset(props: ResetProps): React.JSX.Element { const { getText } = useText() const { diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/Submit.tsx b/app/gui/src/dashboard/components/AriaComponents/Form/components/Submit.tsx index 3593609af1b..e2b93a4186b 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/Submit.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/Submit.tsx @@ -11,9 +11,7 @@ import { useText } from '#/providers/TextProvider' import { useFormContext } from './FormProvider' import type { FormInstance } from './types' -/** - * Additional props for the Submit component. - */ +/** Additional props for the Submit component. */ interface SubmitButtonBaseProps { readonly variant?: ButtonProps['variant'] /** @@ -29,9 +27,7 @@ interface SubmitButtonBaseProps { readonly action?: 'cancel' | 'submit' | 'update' } -/** - * Props for the Submit component. - */ +/** Props for the Submit component. */ export type SubmitProps = Omit & SubmitButtonBaseProps diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/types.ts b/app/gui/src/dashboard/components/AriaComponents/Form/components/types.ts index fd52fcc35da..e095f518736 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/types.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/types.ts @@ -24,17 +24,13 @@ export type TransformedValues = */ export type FieldPath = reactHookForm.FieldPath> -/** - * Schema type - */ +/** Schema type */ export type TSchema = | z.AnyZodObject | z.ZodEffects | z.ZodEffects> -/** - * OnSubmitCallbacks type. - */ +/** OnSubmitCallbacks type. */ export interface OnSubmitCallbacks { readonly onSubmit?: | (( @@ -67,9 +63,7 @@ export interface OnSubmitCallbacks | undefined } -/** - * Props for the useForm hook. - */ +/** Props for the useForm hook. */ export interface UseFormProps extends Omit< reactHookForm.UseFormProps>, @@ -83,16 +77,12 @@ export interface UseFormProps */ readonly canSubmitOffline?: boolean - /** - * Debug name for the form. Use it to identify the form in the tanstack query devtools. - */ + /** Debug name for the form. Use it to identify the form in the tanstack query devtools. */ readonly debugName?: string readonly method?: 'dialog' | (string & {}) | undefined } -/** - * Register function for a form field. - */ +/** Register function for a form field. */ export type UseFormRegister = < TFieldName extends FieldPath = FieldPath, >( @@ -100,9 +90,7 @@ export type UseFormRegister = < options?: reactHookForm.RegisterOptions, TFieldName>, ) => UseFormRegisterReturn -/** - * UseFormRegister return type. - */ +/** UseFormRegister return type. */ export interface UseFormRegisterReturn< Schema extends TSchema, TFieldName extends FieldPath = FieldPath, @@ -147,9 +135,7 @@ export type FormState = reactHookForm.FormState = UseFormReturn -/** - * Form type interface that check if FieldValues type is compatible with the value type from component - */ +/** Form type interface that check if FieldValues type is compatible with the value type from component */ export interface FormWithValueValidation< BaseValueType, Schema extends TSchema, @@ -180,9 +166,7 @@ export type FormInstanceValidated< // eslint-disable-next-line @typescript-eslint/no-explicit-any > = FormInstance | (any[] & NonNullable) -/** - * Props for the Field component. - */ +/** Props for the Field component. */ // Readonly omitted here to avoid type mismatch with native HTML attributes // eslint-disable-next-line no-restricted-syntax export interface FieldProps { @@ -191,27 +175,19 @@ export interface FieldProps { readonly description?: React.ReactNode | undefined readonly error?: React.ReactNode | undefined - /** - * Defines a string value that labels the current element. - */ + /** Defines a string value that labels the current element. */ // eslint-disable-next-line @typescript-eslint/naming-convention 'aria-label'?: string | undefined - /** - * Identifies the element (or elements) that labels the current element. - */ + /** Identifies the element (or elements) that labels the current element. */ // eslint-disable-next-line @typescript-eslint/naming-convention 'aria-labelledby'?: string | undefined - /** - * Identifies the element (or elements) that describes the object. - */ + /** Identifies the element (or elements) that describes the object. */ // eslint-disable-next-line @typescript-eslint/naming-convention 'aria-describedby'?: string | undefined - /** - * Identifies the element (or elements) that provide a detailed, extended description for the object. - */ + /** Identifies the element (or elements) that provide a detailed, extended description for the object. */ // eslint-disable-next-line @typescript-eslint/naming-convention 'aria-details'?: string | undefined } @@ -233,9 +209,7 @@ export interface FormFieldProps< readonly isInvalid?: boolean | undefined } -/** - * Field State Props - */ +/** Field State Props */ export type FieldStateProps< // eslint-disable-next-line no-restricted-syntax BaseProps extends { value?: unknown }, diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/useField.ts b/app/gui/src/dashboard/components/AriaComponents/Form/components/useField.ts index a6c8e7ced12..371c1ce6cfb 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/useField.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/useField.ts @@ -8,9 +8,7 @@ import * as reactHookForm from 'react-hook-form' import * as formContext from './FormProvider' import type * as types from './types' -/** - * Options for {@link useField} hook. - */ +/** Options for {@link useField} hook. */ export interface UseFieldOptions< BaseValueType, Schema extends types.TSchema, @@ -21,9 +19,7 @@ export interface UseFieldOptions< readonly defaultValue?: types.FieldValues[TFieldName] | undefined } -/** - * A hook that connects a field to a form state. - */ +/** A hook that connects a field to a form state. */ export function useField< BaseValueType, Schema extends types.TSchema, diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldRegister.ts b/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldRegister.ts index 6d49080b5f8..9d2dcafd41a 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldRegister.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldRegister.ts @@ -13,9 +13,7 @@ import type { TSchema, } from './types' -/** - * Options for the useFieldRegister hook. - */ +/** Options for the useFieldRegister hook. */ export type UseFieldRegisterOptions< BaseValueType extends { value?: unknown }, Schema extends TSchema, @@ -31,9 +29,7 @@ export type UseFieldRegisterOptions< setValueAs?: ((value: unknown) => unknown) | undefined } -/** - * Registers a field in the form. - */ +/** Registers a field in the form. */ export function useFieldRegister< BaseValueType extends { value?: unknown }, Schema extends TSchema, @@ -62,9 +58,7 @@ export function useFieldRegister< return { fieldProps, formInstance } as const } -/** - * Tried to extract validation details from the schema. - */ +/** Tried to extract validation details from the schema. */ // This name is intentional to highlight that this function is unsafe and should be used with caution. // eslint-disable-next-line camelcase, @typescript-eslint/naming-convention function unsafe__extractValidationDetailsFromSchema< diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldState.ts b/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldState.ts index e1e2e74b222..a83cf45db11 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldState.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/useFieldState.ts @@ -7,9 +7,7 @@ import { useFormState } from 'react-hook-form' import { useFormContext } from './FormProvider' import type { FieldPath, FormInstanceValidated, TSchema } from './types' -/** - * Options for the `useFieldState` hook. - */ +/** Options for the `useFieldState` hook. */ export interface UseFieldStateOptions< Schema extends TSchema, TFieldName extends FieldPath, @@ -18,9 +16,7 @@ export interface UseFieldStateOptions< readonly form?: FormInstanceValidated | undefined } -/** - * Hook to get the state of a field. - */ +/** Hook to get the state of a field. */ export function useFieldState>( options: UseFieldStateOptions, ) { diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/components/useForm.ts b/app/gui/src/dashboard/components/AriaComponents/Form/components/useForm.ts index 80a1ae6b379..923a33bb589 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/components/useForm.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/components/useForm.ts @@ -18,9 +18,7 @@ import { useMutation } from '@tanstack/react-query' import * as schemaModule from './schema' import type * as types from './types' -/** - * Maps the value to the event object. - */ +/** Maps the value to the event object. */ function mapValueOnEvent(value: unknown) { if (typeof value === 'object' && value != null && 'target' in value && 'type' in value) { return value @@ -241,9 +239,7 @@ export function useForm( } } -/** - * Get the type of arguments passed to the useForm hook - */ +/** Get the type of arguments passed to the useForm hook */ function getArgsType( args: types.UseFormProps, ) { diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/styles.ts b/app/gui/src/dashboard/components/AriaComponents/Form/styles.ts index fa681f724de..70df89d8739 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/styles.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/styles.ts @@ -5,9 +5,7 @@ */ import * as twv from '#/utilities/tailwindVariants' -/** - * Props for form components. - */ +/** Props for form components. */ export type FormStyleProps = twv.VariantProps export const FORM_STYLES = twv.tv({ base: 'flex flex-col items-start', diff --git a/app/gui/src/dashboard/components/AriaComponents/Form/types.ts b/app/gui/src/dashboard/components/AriaComponents/Form/types.ts index 2903e2674b8..fcf9257c8d6 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Form/types.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Form/types.ts @@ -14,18 +14,14 @@ import type * as styles from './styles' export type * from './components' -/** - * Props for the Form component - */ +/** Props for the Form component */ export type FormProps< Schema extends components.TSchema, SubmitResult = void, > = BaseFormProps & (FormPropsWithOptions | FormPropsWithParentForm) -/** - * Base props for the Form component. - */ +/** Base props for the Form component. */ interface BaseFormProps extends Omit< React.HTMLProps, @@ -48,9 +44,7 @@ interface BaseFormProps readonly className?: string | ((props: components.UseFormReturn) => string) - /** - * When set to `dialog`, form submission will close the parent dialog on successful submission. - */ + /** When set to `dialog`, form submission will close the parent dialog on successful submission. */ readonly method?: 'dialog' | (NonNullable & string) readonly canSubmitOffline?: boolean @@ -93,9 +87,7 @@ interface FormPropsWithOptions = < TFieldName extends components.FieldPath = components.FieldPath, >( @@ -103,9 +95,7 @@ export type UseFormRegister = < options?: reactHookForm.RegisterOptions, TFieldName>, ) => UseFormRegisterReturn -/** - * UseFormRegister return type. - */ +/** UseFormRegister return type. */ export interface UseFormRegisterReturn< Schema extends components.TSchema, TFieldName extends components.FieldPath = components.FieldPath, @@ -119,9 +109,7 @@ export interface UseFormRegisterReturn< readonly isInvalid?: boolean } -/** - * Form Render Props. - */ +/** Form Render Props. */ export type FormStateRenderProps = Pick< components.FormInstance, | 'clearErrors' diff --git a/app/gui/src/dashboard/components/AriaComponents/Inputs/Input/Input.tsx b/app/gui/src/dashboard/components/AriaComponents/Inputs/Input/Input.tsx index 770624a1787..d8035c5be61 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Inputs/Input/Input.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Inputs/Input/Input.tsx @@ -32,9 +32,7 @@ import type { ExtractFunction, VariantProps } from '#/utilities/tailwindVariants import { omit } from 'enso-common/src/utilities/data/object' import { INPUT_STYLES } from '../variants' -/** - * Props for the Input component. - */ +/** Props for the Input component. */ export interface InputProps> extends FieldStateProps, Schema, TFieldName>, FieldProps, @@ -53,9 +51,7 @@ export interface InputProps['variants'] } -/** - * Basic input component. Input component is a component that is used to get user input in a text field. - */ +/** Basic input component. Input component is a component that is used to get user input in a text field. */ export const Input = forwardRef(function Input< Schema extends TSchema, TFieldName extends FieldPath, diff --git a/app/gui/src/dashboard/components/AriaComponents/Inputs/MultiSelector/MultiSelector.tsx b/app/gui/src/dashboard/components/AriaComponents/Inputs/MultiSelector/MultiSelector.tsx index 17a3e08258b..4b9f8faef02 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Inputs/MultiSelector/MultiSelector.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Inputs/MultiSelector/MultiSelector.tsx @@ -79,9 +79,7 @@ export const MULTI_SELECTOR_STYLES = tv({ }, }) -/** - * A horizontal multi-selector. - */ +/** A horizontal multi-selector. */ export const MultiSelector = forwardRef(function MultiSelector< Schema extends TSchema, TFieldName extends FieldPath, diff --git a/app/gui/src/dashboard/components/AriaComponents/Inputs/OTPInput/OTPInput.tsx b/app/gui/src/dashboard/components/AriaComponents/Inputs/OTPInput/OTPInput.tsx index 7024e6fc088..dc9f40b7594 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Inputs/OTPInput/OTPInput.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Inputs/OTPInput/OTPInput.tsx @@ -1,6 +1,4 @@ -/** - * @file - */ +/** @file */ import { mergeProps } from '#/components/aria' import { mergeRefs } from '#/utilities/mergeRefs' import type { VariantProps } from '#/utilities/tailwindVariants' @@ -23,9 +21,7 @@ import { Separator } from '../../Separator' import { TEXT_STYLE } from '../../Text' import type { TestIdProps } from '../../types' -/** - * Props for an {@link OTPInput}. - */ +/** Props for an {@link OTPInput}. */ export interface OtpInputProps> extends FieldStateProps, Schema, TFieldName>, FieldProps, @@ -40,9 +36,7 @@ export interface OtpInputProps void } @@ -82,9 +76,7 @@ const SLOT_STYLES = tv({ ], }) -/** - * Accessible one-time password component with copy paste functionality. - */ +/** Accessible one-time password component with copy paste functionality. */ export const OTPInput = forwardRef(function OTPInput< Schema extends TSchema, TFieldName extends FieldPath, @@ -196,9 +188,7 @@ export const OTPInput = forwardRef(function OTPInput< ) }) -/** - * Props for a single {@link Slot}. - */ +/** Props for a single {@link Slot}. */ interface SlotProps extends Omit, VariantProps {} /** diff --git a/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx b/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx index 6fa951df8a9..208e53edbb5 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableContentEditableInput.tsx @@ -1,6 +1,4 @@ -/** - * @file A resizable input that uses a content-editable div. - */ +/** @file A resizable input that uses a content-editable div. */ import { useEffect, useRef, @@ -31,9 +29,7 @@ const CONTENT_EDITABLE_STYLES = tv({ slots: { placeholder: 'opacity-50 absolute inset-0 pointer-events-none' }, }) -/** - * Props for a {@link ResizableContentEditableInput}. - */ +/** Props for a {@link ResizableContentEditableInput}. */ export interface ResizableContentEditableInputProps< Schema extends TSchema, TFieldName extends FieldPath, diff --git a/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableInput.tsx b/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableInput.tsx index 91c64373223..6bddfae1114 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableInput.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Inputs/ResizableInput/ResizableInput.tsx @@ -1,6 +1,4 @@ -/** - * @file A resizable input field. - */ +/** @file A resizable input field. */ import * as React from 'react' import * as eventCallbackHooks from '#/hooks/eventCallbackHooks' @@ -12,17 +10,13 @@ import * as mergeRefs from '#/utilities/mergeRefs' import { forwardRef } from '#/utilities/react' import * as variants from '../variants' -/** - * Props for a {@link ResizableInput}. - */ +/** Props for a {@link ResizableInput}. */ export interface ResizableInputProps extends aria.TextFieldProps { readonly placeholder?: string readonly description?: React.ReactNode } -/** - * A resizable input field. - */ +/** A resizable input field. */ export const ResizableInput = forwardRef(function ResizableInput( props: ResizableInputProps, ref: React.ForwardedRef, diff --git a/app/gui/src/dashboard/components/AriaComponents/Inputs/Selector/Selector.tsx b/app/gui/src/dashboard/components/AriaComponents/Inputs/Selector/Selector.tsx index 2f4d5cb05fd..fbb6652711a 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Inputs/Selector/Selector.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Inputs/Selector/Selector.tsx @@ -76,9 +76,7 @@ export const SELECTOR_STYLES = tv({ }, }) -/** - * A horizontal selector. - */ +/** A horizontal selector. */ export const Selector = forwardRef(function Selector< Schema extends TSchema, TFieldName extends FieldPath, diff --git a/app/gui/src/dashboard/components/AriaComponents/Radio/Radio.tsx b/app/gui/src/dashboard/components/AriaComponents/Radio/Radio.tsx index bacc50727c1..af447a83ed7 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Radio/Radio.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Radio/Radio.tsx @@ -44,16 +44,12 @@ const RADIO_STYLES = twv.tv({ ], }) -/** - * Props for the {@link Radio} component. - */ +/** Props for the {@link Radio} component. */ export interface RadioProps extends aria.RadioProps { readonly label?: string } -/** - * A radio button. - */ +/** A radio button. */ // eslint-disable-next-line no-restricted-syntax export const Radio = forwardRef(function Radio( props: RadioProps, diff --git a/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroup.tsx b/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroup.tsx index 3e8f1c59008..9555cdbcbb0 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroup.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroup.tsx @@ -16,9 +16,7 @@ import type { FieldVariantProps } from '../Form' import * as formComponent from '../Form' import * as radioGroupContext from './RadioGroupContext' -/** - * Props for {@link RadioGroup}. - */ +/** Props for {@link RadioGroup}. */ export interface RadioGroupProps< Schema extends formComponent.TSchema, TFieldName extends formComponent.FieldPath, @@ -39,9 +37,7 @@ export const RADIO_GROUP_STYLES = twv.tv({ variants: { fullWidth: { true: 'w-full' } }, }) -/** - * A radio group component. - */ +/** A radio group component. */ // eslint-disable-next-line no-restricted-syntax export const RadioGroup = forwardRef(function RadioGroup< Schema extends formComponent.TSchema, diff --git a/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroupContext.tsx b/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroupContext.tsx index dff78a63121..e5d0a09469b 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroupContext.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Radio/RadioGroupContext.tsx @@ -18,9 +18,7 @@ import invariant from 'tiny-invariant' import * as eventCallback from '#/hooks/eventCallbackHooks' -/** - * Props for {@link RadioGroupContextProps} - */ +/** Props for {@link RadioGroupContextProps} */ export interface RadioGroupContextProps { /** * Tells if a Radio element is being pressed @@ -28,13 +26,9 @@ export interface RadioGroupContextProps { * It's not the same as selected value, instead it stores the value user is clicking on at the moment */ readonly pressedRadio: string | null - /** - * Sets the pressed Radio element - */ + /** Sets the pressed Radio element */ readonly setPressedRadio: (value: string) => void - /** - * Clears the pressed Radio element - */ + /** Clears the pressed Radio element */ readonly clearPressedRadio: () => void } @@ -68,16 +62,12 @@ export function RadioGroupProvider(props: React.PropsWithChildren) { return {children} } -/** - * Props for {@link useRadioGroupContext} - */ +/** Props for {@link useRadioGroupContext} */ export interface UseRadioGroupContextProps { readonly value: string } -/** - * Provides useful information about sibling Radio elements within a RadioGroup - */ +/** Provides useful information about sibling Radio elements within a RadioGroup */ export function useRadioGroupContext(props: UseRadioGroupContextProps) { const { value } = props const context = React.useContext(RadioGroupContext) diff --git a/app/gui/src/dashboard/components/AriaComponents/Separator.tsx b/app/gui/src/dashboard/components/AriaComponents/Separator.tsx index eb8b83d29f0..c0db85abe47 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Separator.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Separator.tsx @@ -9,18 +9,14 @@ import * as aria from '#/components/aria' import * as twv from '#/utilities/tailwindVariants' -/** - * The props for {@link Separator} component. - */ +/** The props for {@link Separator} component. */ export interface SeparatorProps extends aria.SeparatorProps, twv.VariantProps { readonly className?: string } -/** - * The styles for the {@link Separator} component. - */ +/** The styles for the {@link Separator} component. */ export const SEPARATOR_STYLES = twv.tv({ base: 'rounded-full border-none', variants: { @@ -79,9 +75,7 @@ export const SEPARATOR_STYLES = twv.tv({ ], }) -/** - * A separator component. - */ +/** A separator component. */ export function Separator(props: SeparatorProps) { const { orientation = 'horizontal', variant, className, size, ...rest } = props diff --git a/app/gui/src/dashboard/components/AriaComponents/Switch/Switch.tsx b/app/gui/src/dashboard/components/AriaComponents/Switch/Switch.tsx index 7145ae73cc7..23ff030cf8e 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Switch/Switch.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Switch/Switch.tsx @@ -16,9 +16,7 @@ import { tv, type VariantProps } from '#/utilities/tailwindVariants' import { Form, type FieldPath, type FieldProps, type FieldStateProps, type TSchema } from '../Form' import { TEXT_STYLE } from '../Text' -/** - * Props for the {@Switch} component. - */ +/** Props for the {@Switch} component. */ export interface SwitchProps> extends FieldStateProps< Omit & { value: boolean }, @@ -59,9 +57,7 @@ export const SWITCH_STYLES = tv({ }, }) -/** - * A switch allows a user to turn a setting on or off. - */ +/** A switch allows a user to turn a setting on or off. */ // eslint-disable-next-line no-restricted-syntax export const Switch = forwardRef(function Switch< Schema extends TSchema, diff --git a/app/gui/src/dashboard/components/AriaComponents/Text/Text.tsx b/app/gui/src/dashboard/components/AriaComponents/Text/Text.tsx index 2e7f2495401..c103c7e3a13 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Text/Text.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Text/Text.tsx @@ -1,6 +1,4 @@ -/** - * @file Text component - */ +/** @file Text component */ import * as React from 'react' import * as aria from '#/components/aria' @@ -12,9 +10,7 @@ import { forwardRef } from '#/utilities/react' import * as textProvider from './TextProvider' import * as visualTooltip from './useVisualTooltip' -/** - * Props for the Text component - */ +/** Props for the Text component */ export interface TextProps extends Omit, twv.VariantProps { @@ -119,9 +115,7 @@ export const TEXT_STYLE = twv.tv({ }, }) -/** - * Text component that supports truncation and show a tooltip on hover when text is truncated - */ +/** Text component that supports truncation and show a tooltip on hover when text is truncated */ // eslint-disable-next-line no-restricted-syntax export const Text = forwardRef(function Text(props: TextProps, ref: React.Ref) { const { @@ -216,17 +210,13 @@ export const Text = forwardRef(function Text(props: TextProps, ref: React.Ref } -/** - * Heading props - */ +/** Heading props */ export interface HeadingProps extends Omit { // eslint-disable-next-line @typescript-eslint/no-magic-numbers readonly level?: '1' | '2' | '3' | '4' | '5' | '6' | 1 | 2 | 3 | 4 | 5 | 6 } -/** - * Heading component - */ +/** Heading component */ // eslint-disable-next-line no-restricted-syntax const Heading = forwardRef(function Heading( props: HeadingProps, @@ -237,9 +227,7 @@ const Heading = forwardRef(function Heading( }) Text.Heading = Heading -/** - * Text group component. It's used to visually group text elements together - */ +/** Text group component. It's used to visually group text elements together */ Text.Group = function TextGroup(props: React.PropsWithChildren) { return ( diff --git a/app/gui/src/dashboard/components/AriaComponents/Text/TextProvider.tsx b/app/gui/src/dashboard/components/AriaComponents/Text/TextProvider.tsx index 5e1e8081442..e91ecd25ec8 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Text/TextProvider.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Text/TextProvider.tsx @@ -5,13 +5,9 @@ */ import * as React from 'react' -/** - * Context for the Text component. - */ +/** Context for the Text component. */ export interface TextContextType { - /** - * Flag indicating whether the component is inside a Text component. - */ + /** Flag indicating whether the component is inside a Text component. */ readonly isInsideTextComponent: boolean } @@ -19,9 +15,7 @@ const TextContext = React.createContext({ isInsideTextComponent: false, }) -/** - * Hook to get the Text context. - */ +/** Hook to get the Text context. */ export function useTextContext(): TextContextType { return React.useContext(TextContext) } diff --git a/app/gui/src/dashboard/components/AriaComponents/Text/useVisualTooltip.tsx b/app/gui/src/dashboard/components/AriaComponents/Text/useVisualTooltip.tsx index 7e764385b3d..311912d2088 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Text/useVisualTooltip.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Text/useVisualTooltip.tsx @@ -12,9 +12,7 @@ import * as aria from '#/components/aria' import * as ariaComponents from '#/components/AriaComponents' import Portal from '#/components/Portal' -/** - * Props for {@link useVisualTooltip}. - */ +/** Props for {@link useVisualTooltip}. */ export interface VisualTooltipProps extends Pick { readonly children: React.ReactNode @@ -41,9 +39,7 @@ export interface VisualTooltipReturn { readonly tooltip: JSX.Element | null } -/** - * The display strategy for the tooltip. - */ +/** The display strategy for the tooltip. */ type DisplayStrategy = 'always' | 'whenOverflowing' const DEFAULT_OFFSET = 6 diff --git a/app/gui/src/dashboard/components/AriaComponents/VisuallyHidden.tsx b/app/gui/src/dashboard/components/AriaComponents/VisuallyHidden.tsx index 9198d3d4317..60630c071e6 100644 --- a/app/gui/src/dashboard/components/AriaComponents/VisuallyHidden.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/VisuallyHidden.tsx @@ -8,16 +8,12 @@ import * as React from 'react' import { forwardRef } from '#/utilities/react' import * as twv from '#/utilities/tailwindVariants' -/** - * Props for the {@link VisuallyHidden} component. - */ +/** Props for the {@link VisuallyHidden} component. */ export type VisuallyHiddenProps = React.HTMLProps export const VISUALLY_HIDDEN_STYLES = twv.tv({ base: 'sr-only' }) -/** - * A component visually hides its children from the screen, but keeps them accessible to screen readers. - */ +/** A component visually hides its children from the screen, but keeps them accessible to screen readers. */ // eslint-disable-next-line no-restricted-syntax export const VisuallyHidden = forwardRef(function VisuallyHidden( props: VisuallyHiddenProps, diff --git a/app/gui/src/dashboard/components/AriaComponents/types.ts b/app/gui/src/dashboard/components/AriaComponents/types.ts index 6f2e2dcb5eb..6cee783f223 100644 --- a/app/gui/src/dashboard/components/AriaComponents/types.ts +++ b/app/gui/src/dashboard/components/AriaComponents/types.ts @@ -4,13 +4,9 @@ * Common types for ARIA components. */ -/** - * Props for adding a test id to a component - */ +/** Props for adding a test id to a component */ export interface TestIdProps { - /** - * @deprecated Use `testid` instead - */ + /** @deprecated Use `testid` instead */ readonly 'data-testid'?: string | undefined readonly testId?: string | undefined } diff --git a/app/gui/src/dashboard/components/Badge/Badge.tsx b/app/gui/src/dashboard/components/Badge/Badge.tsx index 72c7e628be2..f4b594e9902 100644 --- a/app/gui/src/dashboard/components/Badge/Badge.tsx +++ b/app/gui/src/dashboard/components/Badge/Badge.tsx @@ -8,9 +8,7 @@ import { tv } from '#/utilities/tailwindVariants' import type { ReactNode } from 'react' import { TEXT_STYLE } from '../AriaComponents' -/** - * Props for the {@link Badge} component. - */ +/** Props for the {@link Badge} component. */ export interface BadgeProps extends VariantProps { readonly children?: ReactNode readonly className?: string @@ -61,9 +59,7 @@ export const BADGE_STYLES = tv({ }, }) -/** - * Badges are used to highlight an item's status for quick recognition. - */ +/** Badges are used to highlight an item's status for quick recognition. */ export function Badge(props: BadgeProps) { const { children, color, rounded, className, variant } = props diff --git a/app/gui/src/dashboard/components/Devtools/EnsoDevtools.tsx b/app/gui/src/dashboard/components/Devtools/EnsoDevtools.tsx index 9d10f73affe..fbe1a7f1e65 100644 --- a/app/gui/src/dashboard/components/Devtools/EnsoDevtools.tsx +++ b/app/gui/src/dashboard/components/Devtools/EnsoDevtools.tsx @@ -48,9 +48,7 @@ import * as backend from '#/services/Backend' import LocalStorage, { type LocalStorageData } from '#/utilities/LocalStorage' import { unsafeEntries } from 'enso-common/src/utilities/data/object' -/** - * A component that provides a UI for toggling paywall features. - */ +/** A component that provides a UI for toggling paywall features. */ export function EnsoDevtools() { const { getText } = textProvider.useText() const { authQueryKey, session } = authProvider.useAuth() diff --git a/app/gui/src/dashboard/components/Devtools/EnsoDevtoolsProvider.tsx b/app/gui/src/dashboard/components/Devtools/EnsoDevtoolsProvider.tsx index ba6829dc60f..213db13bb8e 100644 --- a/app/gui/src/dashboard/components/Devtools/EnsoDevtoolsProvider.tsx +++ b/app/gui/src/dashboard/components/Devtools/EnsoDevtoolsProvider.tsx @@ -5,9 +5,7 @@ import type { PaywallFeatureName } from '#/hooks/billing' import * as zustand from 'zustand' -/** - * Configuration for a paywall feature. - */ +/** Configuration for a paywall feature. */ export interface PaywallDevtoolsFeatureConfiguration { readonly isForceEnabled: boolean | null } @@ -62,9 +60,7 @@ export function useSetEnableVersionChecker() { return zustand.useStore(ensoDevtoolsStore, (state) => state.setEnableVersionChecker) } -/** - * A hook that provides access to the paywall devtools. - */ +/** A hook that provides access to the paywall devtools. */ export function usePaywallDevtools() { return zustand.useStore(ensoDevtoolsStore, (state) => ({ features: state.paywallFeatures, diff --git a/app/gui/src/dashboard/components/Devtools/ReactQueryDevtools.tsx b/app/gui/src/dashboard/components/Devtools/ReactQueryDevtools.tsx index 5d681123853..e786fec979a 100644 --- a/app/gui/src/dashboard/components/Devtools/ReactQueryDevtools.tsx +++ b/app/gui/src/dashboard/components/Devtools/ReactQueryDevtools.tsx @@ -1,6 +1,4 @@ -/** - * @file Show the React Query Devtools. - */ +/** @file Show the React Query Devtools. */ import * as React from 'react' import * as reactQuery from '@tanstack/react-query' diff --git a/app/gui/src/dashboard/components/OfflineNotificationManager.tsx b/app/gui/src/dashboard/components/OfflineNotificationManager.tsx index 21ac6bb27b5..5776459e34c 100644 --- a/app/gui/src/dashboard/components/OfflineNotificationManager.tsx +++ b/app/gui/src/dashboard/components/OfflineNotificationManager.tsx @@ -14,14 +14,10 @@ import * as offlineHooks from '#/hooks/offlineHooks' import * as textProvider from '#/providers/TextProvider' -/** - * Props for {@link OfflineNotificationManager} - */ +/** Props for {@link OfflineNotificationManager} */ export type OfflineNotificationManagerProps = Readonly -/** - * Context props for {@link OfflineNotificationManager} - */ +/** Context props for {@link OfflineNotificationManager} */ interface OfflineNotificationManagerContextProps { readonly isNested: boolean readonly toastId?: string @@ -30,9 +26,7 @@ interface OfflineNotificationManagerContextProps { const OfflineNotificationManagerContext = React.createContext({ isNested: false }) -/** - * Offline Notification Manager component. - */ +/** Offline Notification Manager component. */ export function OfflineNotificationManager(props: OfflineNotificationManagerProps) { const { children } = props const toastId = 'offline' diff --git a/app/gui/src/dashboard/components/Paywall/ContextMenuEntry.tsx b/app/gui/src/dashboard/components/Paywall/ContextMenuEntry.tsx index d97f1e05af9..ae01e283d96 100644 --- a/app/gui/src/dashboard/components/Paywall/ContextMenuEntry.tsx +++ b/app/gui/src/dashboard/components/Paywall/ContextMenuEntry.tsx @@ -17,17 +17,13 @@ import ContextMenuEntryBase from '#/components/ContextMenuEntry' import * as paywallDialog from './PaywallDialog' -/** - * Props for {@link ContextMenuEntry}. - */ +/** Props for {@link ContextMenuEntry}. */ export interface ContextMenuEntryProps extends Omit { readonly feature: billingHooks.PaywallFeatureName } -/** - * A context menu entry that opens a paywall dialog. - */ +/** A context menu entry that opens a paywall dialog. */ export function ContextMenuEntry(props: ContextMenuEntryProps) { const { feature, ...rest } = props const { setModal } = modalProvider.useSetModal() diff --git a/app/gui/src/dashboard/components/Paywall/PaywallAlert.tsx b/app/gui/src/dashboard/components/Paywall/PaywallAlert.tsx index a92f3493b6e..ffaaa7339bf 100644 --- a/app/gui/src/dashboard/components/Paywall/PaywallAlert.tsx +++ b/app/gui/src/dashboard/components/Paywall/PaywallAlert.tsx @@ -16,9 +16,7 @@ import * as ariaComponents from '#/components/AriaComponents' import * as paywall from '#/components/Paywall' import SvgMask from '#/components/SvgMask' -/** - * Props for {@link PaywallAlert}. - */ +/** Props for {@link PaywallAlert}. */ export interface PaywallAlertProps extends Omit { readonly feature: billingHooks.PaywallFeatureName readonly label: string @@ -26,9 +24,7 @@ export interface PaywallAlertProps extends Omit } -/** - * A paywall alert. - */ +/** A paywall alert. */ export function PaywallAlert(props: PaywallAlertProps) { const { label, diff --git a/app/gui/src/dashboard/components/Paywall/PaywallDialog.tsx b/app/gui/src/dashboard/components/Paywall/PaywallDialog.tsx index fc8be6a5cd5..c3cbb7278fc 100644 --- a/app/gui/src/dashboard/components/Paywall/PaywallDialog.tsx +++ b/app/gui/src/dashboard/components/Paywall/PaywallDialog.tsx @@ -15,16 +15,12 @@ import * as ariaComponents from '#/components/AriaComponents' import * as components from './components' import * as upgradeButton from './UpgradeButton' -/** - * Props for a {@link PaywallDialog}. - */ +/** Props for a {@link PaywallDialog}. */ export interface PaywallDialogProps extends ariaComponents.DialogProps { readonly feature: billingHooks.PaywallFeatureName } -/** - * A dialog that prompts the user to upgrade to a paid plan. - */ +/** A dialog that prompts the user to upgrade to a paid plan. */ export function PaywallDialog(props: PaywallDialogProps) { const { feature, type = 'modal', title, ...dialogProps } = props diff --git a/app/gui/src/dashboard/components/Paywall/PaywallDialogButton.tsx b/app/gui/src/dashboard/components/Paywall/PaywallDialogButton.tsx index 75b90ffa2f3..457fcf0b825 100644 --- a/app/gui/src/dashboard/components/Paywall/PaywallDialogButton.tsx +++ b/app/gui/src/dashboard/components/Paywall/PaywallDialogButton.tsx @@ -11,18 +11,14 @@ import * as ariaComponents from '#/components/AriaComponents' import * as components from './components' import * as paywallDialog from './PaywallDialog' -/** - * Props for a {@link PaywallDialogButton}. - */ +/** Props for a {@link PaywallDialogButton}. */ // eslint-disable-next-line no-restricted-syntax export type PaywallDialogButtonProps = components.PaywallButtonProps & { readonly dialogProps?: paywallDialog.PaywallDialogProps readonly dialogTriggerProps?: ariaComponents.DialogTriggerProps } -/** - * A button that opens a paywall dialog when clicked - */ +/** A button that opens a paywall dialog when clicked */ export function PaywallDialogButton(props: PaywallDialogButtonProps) { const { feature, dialogProps, dialogTriggerProps, ...buttonProps } = props diff --git a/app/gui/src/dashboard/components/Paywall/PaywallScreen.tsx b/app/gui/src/dashboard/components/Paywall/PaywallScreen.tsx index 361d714c749..34ce0b534b4 100644 --- a/app/gui/src/dashboard/components/Paywall/PaywallScreen.tsx +++ b/app/gui/src/dashboard/components/Paywall/PaywallScreen.tsx @@ -17,17 +17,13 @@ import * as ariaComponents from '#/components/AriaComponents' import * as components from './components' import * as upgradeButton from './UpgradeButton' -/** - * Props for a {@link PaywallScreen}. - */ +/** Props for a {@link PaywallScreen}. */ export interface PaywallScreenProps { readonly feature: billingHooks.PaywallFeatureName readonly className?: string } -/** - * A screen that shows a paywall. - */ +/** A screen that shows a paywall. */ export function PaywallScreen(props: PaywallScreenProps) { const { feature, className } = props const { getText } = textProvider.useText() diff --git a/app/gui/src/dashboard/components/Paywall/UpgradeButton.tsx b/app/gui/src/dashboard/components/Paywall/UpgradeButton.tsx index ec1cb9ebd15..daa254fcf90 100644 --- a/app/gui/src/dashboard/components/Paywall/UpgradeButton.tsx +++ b/app/gui/src/dashboard/components/Paywall/UpgradeButton.tsx @@ -13,18 +13,14 @@ import * as textProvider from '#/providers/TextProvider' import * as ariaComponents from '#/components/AriaComponents' -/** - * Props for an {@link UpgradeButton}. - */ +/** Props for an {@link UpgradeButton}. */ // eslint-disable-next-line no-restricted-syntax export type UpgradeButtonProps = Omit & { readonly feature: billingHooks.PaywallFeatureName readonly variant?: ariaComponents.ButtonProps['variant'] } -/** - * A button that links to the upgrade page. - */ +/** A button that links to the upgrade page. */ export function UpgradeButton(props: UpgradeButtonProps) { const { feature, diff --git a/app/gui/src/dashboard/components/Paywall/components/PaywallBulletPoints.tsx b/app/gui/src/dashboard/components/Paywall/components/PaywallBulletPoints.tsx index b6df80165db..cae54ed1100 100644 --- a/app/gui/src/dashboard/components/Paywall/components/PaywallBulletPoints.tsx +++ b/app/gui/src/dashboard/components/Paywall/components/PaywallBulletPoints.tsx @@ -16,17 +16,13 @@ import * as textProvider from '#/providers/TextProvider' import * as ariaComponents from '#/components/AriaComponents' import SvgMask from '#/components/SvgMask' -/** - * Props for a {@link PaywallBulletPoints}. - */ +/** Props for a {@link PaywallBulletPoints}. */ export interface PaywallBulletPointsProps { readonly bulletPointsTextId: text.TextId readonly className?: string } -/** - * A component that renders a list of bullet points for a paywall. - */ +/** A component that renders a list of bullet points for a paywall. */ export function PaywallBulletPoints(props: PaywallBulletPointsProps) { const { bulletPointsTextId, className } = props diff --git a/app/gui/src/dashboard/components/Paywall/components/PaywallButton.tsx b/app/gui/src/dashboard/components/Paywall/components/PaywallButton.tsx index 4fa45f1dd32..150856e1c9a 100644 --- a/app/gui/src/dashboard/components/Paywall/components/PaywallButton.tsx +++ b/app/gui/src/dashboard/components/Paywall/components/PaywallButton.tsx @@ -13,9 +13,7 @@ import * as textProvider from '#/providers/TextProvider' import * as ariaComponents from '#/components/AriaComponents' -/** - * Props for {@link PaywallButton}. - */ +/** Props for {@link PaywallButton}. */ // eslint-disable-next-line no-restricted-syntax export type PaywallButtonProps = ariaComponents.ButtonProps & { readonly feature: billingHooks.PaywallFeatureName @@ -23,9 +21,7 @@ export type PaywallButtonProps = ariaComponents.ButtonProps & { readonly showIcon?: boolean } -/** - * A styled button that shows that a feature is behind a paywall - */ +/** A styled button that shows that a feature is behind a paywall */ export function PaywallButton(props: PaywallButtonProps) { const { feature, iconOnly = false, showIcon = true, children, ...buttonProps } = props diff --git a/app/gui/src/dashboard/components/Paywall/components/PaywallLock.tsx b/app/gui/src/dashboard/components/Paywall/components/PaywallLock.tsx index be4bdbba749..5c59a053fd1 100644 --- a/app/gui/src/dashboard/components/Paywall/components/PaywallLock.tsx +++ b/app/gui/src/dashboard/components/Paywall/components/PaywallLock.tsx @@ -1,6 +1,4 @@ -/** - * @file A lock icon with a label indicating the paywall level required to access a feature. - */ +/** @file A lock icon with a label indicating the paywall level required to access a feature. */ import * as tw from 'tailwind-merge' import LockIcon from '#/assets/lock.svg' @@ -12,17 +10,13 @@ import * as textProvider from '#/providers/TextProvider' import * as ariaComponents from '#/components/AriaComponents' import SvgMask from '#/components/SvgMask' -/** - * Props for a {@link PaywallLock}. - */ +/** Props for a {@link PaywallLock}. */ export interface PaywallLockProps { readonly feature: billingHooks.PaywallFeatureName readonly className?: string } -/** - * A lock icon with a label indicating the paywall level required to access a feature. - */ +/** A lock icon with a label indicating the paywall level required to access a feature. */ export function PaywallLock(props: PaywallLockProps) { const { feature, className } = props const { getText } = textProvider.useText() diff --git a/app/gui/src/dashboard/components/Portal/PortalProvider.ts b/app/gui/src/dashboard/components/Portal/PortalProvider.ts index 9c9e2cc77f1..661d00b2ded 100644 --- a/app/gui/src/dashboard/components/Portal/PortalProvider.ts +++ b/app/gui/src/dashboard/components/Portal/PortalProvider.ts @@ -8,9 +8,7 @@ import invariant from 'tiny-invariant' const PortalContext = React.createContext(null) -/** - * Allows to access the root element for the Portal component - */ +/** Allows to access the root element for the Portal component */ export function usePortalContext() { const root = React.useContext(PortalContext) @@ -29,8 +27,6 @@ export function useStrictPortalContext() { return root } -/** - * Specifies the root element for the Portal component - */ +/** Specifies the root element for the Portal component */ // eslint-disable-next-line no-restricted-syntax export const PortalProvider = PortalContext.Provider diff --git a/app/gui/src/dashboard/components/Portal/types.ts b/app/gui/src/dashboard/components/Portal/types.ts index 3d8bea2be92..d51348fac4a 100644 --- a/app/gui/src/dashboard/components/Portal/types.ts +++ b/app/gui/src/dashboard/components/Portal/types.ts @@ -4,9 +4,7 @@ */ import type * as React from 'react' -/** - * The props for the Portal component - */ +/** The props for the Portal component */ export interface PortalProps { /** * Ref, where `` should render its children @@ -19,9 +17,7 @@ export interface PortalProps { * @default false */ readonly isDisabled?: boolean - /** - * Callback, will be called after portal's children mounted - */ + /** Callback, will be called after portal's children mounted */ readonly onMount?: () => void readonly children?: React.ReactNode } diff --git a/app/gui/src/dashboard/components/Stepper/Step.tsx b/app/gui/src/dashboard/components/Stepper/Step.tsx index 43876d52e65..b9e76398814 100644 --- a/app/gui/src/dashboard/components/Stepper/Step.tsx +++ b/app/gui/src/dashboard/components/Stepper/Step.tsx @@ -19,9 +19,7 @@ import type * as stepperState from './useStepperState' /** A prop with the given type, or a function to produce a value of the given type. */ type StepProp = T | ((props: RenderStepProps) => T) -/** - * Props for {@link Step} component. - */ +/** Props for {@link Step} component. */ export interface StepProps extends RenderStepProps { readonly className?: StepProp readonly icon?: StepProp @@ -52,9 +50,7 @@ const STEP_STYLES = tv({ }, }) -/** - * A step component is used to represent a single step in a stepper component. - */ +/** A step component is used to represent a single step in a stepper component. */ export function Step(props: StepProps) { const { index, diff --git a/app/gui/src/dashboard/components/Stepper/StepContent.tsx b/app/gui/src/dashboard/components/Stepper/StepContent.tsx index 3a4c98395d0..6e602e10162 100644 --- a/app/gui/src/dashboard/components/Stepper/StepContent.tsx +++ b/app/gui/src/dashboard/components/Stepper/StepContent.tsx @@ -6,18 +6,14 @@ import type { ReactElement, ReactNode } from 'react' import { useStepperContext } from './StepperProvider' import type { RenderChildrenProps } from './types' -/** - * Props for {@link StepContent} component. - */ +/** Props for {@link StepContent} component. */ export interface StepContentProps { readonly index: number readonly children: ReactNode | ((props: RenderChildrenProps) => ReactNode) readonly forceRender?: boolean } -/** - * Step content component. Renders the step content if the step is current or if `forceRender` is true. - */ +/** Step content component. Renders the step content if the step is current or if `forceRender` is true. */ export function StepContent(props: StepContentProps): ReactElement | null { const { index, children, forceRender = false } = props const { currentStep, goToStep, nextStep, previousStep, totalSteps } = useStepperContext() diff --git a/app/gui/src/dashboard/components/Stepper/Stepper.tsx b/app/gui/src/dashboard/components/Stepper/Stepper.tsx index e8bfdf98bd0..36e3a7fbc84 100644 --- a/app/gui/src/dashboard/components/Stepper/Stepper.tsx +++ b/app/gui/src/dashboard/components/Stepper/Stepper.tsx @@ -19,9 +19,7 @@ import * as stepperProvider from './StepperProvider' import type { BaseRenderProps, RenderChildrenProps, RenderStepProps } from './types' import * as stepperState from './useStepperState' -/** - * Props for {@link Stepper} component. - */ +/** Props for {@link Stepper} component. */ export interface StepperProps { readonly state: stepperState.StepperState readonly children: React.ReactNode | ((props: RenderChildrenProps) => React.ReactNode) @@ -48,9 +46,7 @@ const STEPPER_STYLES = tv({ const ANIMATION_OFFSET = 15 -/** - * A stepper component is used to indicate progress through a multi-step process. - */ +/** A stepper component is used to indicate progress through a multi-step process. */ export function Stepper(props: StepperProps) { const { renderStep, children, state } = props @@ -77,9 +73,7 @@ export function Stepper(props: StepperProps) { const style = typeof props.style === 'function' ? props.style(baseRenderProps) : props.style - /** - * Render children of the stepper component. - */ + /** Render children of the stepper component. */ const renderChildren = () => { const renderProps = { currentStep, diff --git a/app/gui/src/dashboard/components/Stepper/StepperProvider.tsx b/app/gui/src/dashboard/components/Stepper/StepperProvider.tsx index 0d9d2175464..572a26bd03d 100644 --- a/app/gui/src/dashboard/components/Stepper/StepperProvider.tsx +++ b/app/gui/src/dashboard/components/Stepper/StepperProvider.tsx @@ -9,9 +9,7 @@ import invariant from 'tiny-invariant' import type { StepperState } from './useStepperState' -/** - * StepperProvider props - */ +/** StepperProvider props */ export interface StepperContextType { readonly currentStep: number readonly goToStep: (step: number) => void diff --git a/app/gui/src/dashboard/components/Stepper/types.ts b/app/gui/src/dashboard/components/Stepper/types.ts index 3873571959b..326629476ac 100644 --- a/app/gui/src/dashboard/components/Stepper/types.ts +++ b/app/gui/src/dashboard/components/Stepper/types.ts @@ -4,9 +4,7 @@ * Types for the stepper component. */ -/** - * Render props for the stepper component. - */ +/** Render props for the stepper component. */ export interface BaseRenderProps { readonly goToStep: (step: number) => void readonly nextStep: () => void @@ -15,21 +13,15 @@ export interface BaseRenderProps { readonly totalSteps: number } -/** - * Render props for rendering children of the stepper component. - */ +/** Render props for rendering children of the stepper component. */ export interface RenderChildrenProps extends BaseRenderProps { readonly isFirst: boolean readonly isLast: boolean } -/** - * Render props for lazy rendering of steps. - */ +/** Render props for lazy rendering of steps. */ export interface RenderStepProps extends BaseRenderProps { - /** - * The index of the step, starting from 0. - */ + /** The index of the step, starting from 0. */ readonly index: number readonly isCurrent: boolean readonly isCompleted: boolean diff --git a/app/gui/src/dashboard/components/Stepper/useStepperState.ts b/app/gui/src/dashboard/components/Stepper/useStepperState.ts index 72ff5412c28..216646c62fb 100644 --- a/app/gui/src/dashboard/components/Stepper/useStepperState.ts +++ b/app/gui/src/dashboard/components/Stepper/useStepperState.ts @@ -9,30 +9,20 @@ import invariant from 'tiny-invariant' import * as eventCallbackHooks from '#/hooks/eventCallbackHooks' -/** - * Direction of the stepper - */ +/** Direction of the stepper */ type Direction = 'back-none' | 'back' | 'forward-none' | 'forward' | 'initial' -/** - * Props for {@link useStepperState} - */ +/** Props for {@link useStepperState} */ export interface StepperStateProps { - /** - * The default step to start on (0-indexed) - */ + /** The default step to start on (0-indexed) */ readonly defaultStep?: number - /** - * The number of steps in the stepper (amount of steps is 1-indexed) - */ + /** The number of steps in the stepper (amount of steps is 1-indexed) */ readonly steps: number readonly onStepChange?: (step: number, direction: 'back' | 'forward') => void readonly onCompleted?: () => void } -/** - * State for a stepper component - */ +/** State for a stepper component */ export interface StepperState { readonly currentStep: number readonly onStepChange: (step: number) => void @@ -43,9 +33,7 @@ export interface StepperState { readonly percentComplete: number } -/** - * Result of {@link useStepperState} - */ +/** Result of {@link useStepperState} */ export interface UseStepperStateResult { readonly stepperState: StepperState readonly direction: Direction diff --git a/app/gui/src/dashboard/components/Suspense.tsx b/app/gui/src/dashboard/components/Suspense.tsx index 34de06f7b28..5b97c232a8f 100644 --- a/app/gui/src/dashboard/components/Suspense.tsx +++ b/app/gui/src/dashboard/components/Suspense.tsx @@ -18,9 +18,7 @@ import * as result from '#/components/Result' import * as loader from './Loader' -/** - * Props for {@link Suspense} component. - */ +/** Props for {@link Suspense} component. */ export interface SuspenseProps extends React.SuspenseProps { readonly loaderProps?: loader.LoaderProps readonly offlineFallback?: React.ReactNode diff --git a/app/gui/src/dashboard/hooks/autoFocusHooks.ts b/app/gui/src/dashboard/hooks/autoFocusHooks.ts index 9244b6d4542..a765d0b0df7 100644 --- a/app/gui/src/dashboard/hooks/autoFocusHooks.ts +++ b/app/gui/src/dashboard/hooks/autoFocusHooks.ts @@ -7,9 +7,7 @@ import { useInteractOutside } from '#/components/aria' import { useEffect, useRef } from 'react' import { useEventCallback } from './eventCallbackHooks' -/** - * Props for the {@link useAutoFocus} hook. - */ +/** Props for the {@link useAutoFocus} hook. */ export interface UseAutoFocusProps { readonly ref: React.RefObject readonly disabled?: boolean | undefined diff --git a/app/gui/src/dashboard/hooks/billing/FeaturesConfiguration.ts b/app/gui/src/dashboard/hooks/billing/FeaturesConfiguration.ts index b9bc0d5fb52..4010686bc04 100644 --- a/app/gui/src/dashboard/hooks/billing/FeaturesConfiguration.ts +++ b/app/gui/src/dashboard/hooks/billing/FeaturesConfiguration.ts @@ -8,9 +8,7 @@ import type * as text from 'enso-common/src/text' import * as backend from '#/services/Backend' -/** - * Registered paywall features. - */ +/** Registered paywall features. */ export const PAYWALL_FEATURES = { userGroups: 'userGroups', userGroupsFull: 'userGroupsFull', @@ -20,14 +18,10 @@ export const PAYWALL_FEATURES = { shareFull: 'shareFull', } as const -/** - * Paywall features. - */ +/** Paywall features. */ export type PaywallFeatureName = keyof typeof PAYWALL_FEATURES -/** - * Paywall level names - */ +/** Paywall level names */ export type PaywallLevelName = backend.Plan | 'free' /** @@ -42,9 +36,7 @@ export type PaywallLevelValue = | (2 & { readonly name: PaywallLevelName; readonly label: text.TextId }) | (3 & { readonly name: PaywallLevelName; readonly label: text.TextId }) -/** - * Paywall levels configuration. - */ +/** Paywall levels configuration. */ export const PAYWALL_LEVELS: Record = { free: Object.assign(0, { name: 'free', label: 'freePlanName' } as const), [backend.Plan.solo]: Object.assign(1, { @@ -61,14 +53,10 @@ export const PAYWALL_LEVELS: Record = { } as const), } -/** - * - */ +/** Possible paywall unlock states for a user account. */ export type PaywallLevel = (typeof PAYWALL_LEVELS)[keyof typeof PAYWALL_LEVELS] -/** - * Paywall feature labels. - */ +/** Paywall feature labels. */ const PAYWALL_FEATURES_LABELS: Record = { userGroups: 'userGroupsFeatureLabel', userGroupsFull: 'userGroupsFullFeatureLabel', @@ -88,18 +76,14 @@ const PAYWALL_FEATURE_META = { shareFull: undefined, } satisfies { [K in PaywallFeatureName]: unknown } -/** - * Basic feature configuration. - */ +/** Basic feature configuration. */ interface BasicFeatureConfiguration { readonly level: PaywallLevel readonly bulletPointsTextId: `${PaywallFeatureName}FeatureBulletPoints` readonly descriptionTextId: `${PaywallFeatureName}FeatureDescription` } -/** - * Feature configuration. - */ +/** Feature configuration. */ export type FeatureConfiguration = BasicFeatureConfiguration & { readonly name: Key @@ -140,23 +124,17 @@ const PAYWALL_CONFIGURATION: Record( feature: Key, ): FeatureConfiguration { diff --git a/app/gui/src/dashboard/hooks/billing/paywallFeaturesHooks.ts b/app/gui/src/dashboard/hooks/billing/paywallFeaturesHooks.ts index ee70a8be879..89d228ab2b7 100644 --- a/app/gui/src/dashboard/hooks/billing/paywallFeaturesHooks.ts +++ b/app/gui/src/dashboard/hooks/billing/paywallFeaturesHooks.ts @@ -8,9 +8,7 @@ import * as eventCallbackHooks from '#/hooks/eventCallbackHooks' import * as paywallFeaturesConfiguration from './FeaturesConfiguration' -/** - * A hook that provides access to the paywall features configuration. - */ +/** A hook that provides access to the paywall features configuration. */ export function usePaywallFeatures() { const getFeature = eventCallbackHooks.useEventCallback( (feature: Key) => { diff --git a/app/gui/src/dashboard/hooks/billing/paywallHooks.ts b/app/gui/src/dashboard/hooks/billing/paywallHooks.ts index 0ee080eb300..7f22831697e 100644 --- a/app/gui/src/dashboard/hooks/billing/paywallHooks.ts +++ b/app/gui/src/dashboard/hooks/billing/paywallHooks.ts @@ -12,17 +12,13 @@ import type * as backend from '#/services/Backend' import * as paywallConfiguration from './FeaturesConfiguration' import * as paywallFeatures from './paywallFeaturesHooks' -/** - * Props for the {@link usePaywall} hook. - */ +/** Props for the {@link usePaywall} hook. */ export interface UsePaywallProps { // eslint-disable-next-line no-restricted-syntax readonly plan?: backend.Plan | undefined } -/** - * A hook that provides paywall-related functionality. - */ +/** A hook that provides paywall-related functionality. */ export function usePaywall(props: UsePaywallProps) { const { plan } = props diff --git a/app/gui/src/dashboard/hooks/copyHooks.ts b/app/gui/src/dashboard/hooks/copyHooks.ts index 5ef90872cf0..54286feb1a2 100644 --- a/app/gui/src/dashboard/hooks/copyHooks.ts +++ b/app/gui/src/dashboard/hooks/copyHooks.ts @@ -13,9 +13,7 @@ import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' import * as textProvider from '#/providers/TextProvider' -/** - * Props for the useCopy hook. - */ +/** Props for the useCopy hook. */ export interface UseCopyProps { readonly copyText: string readonly onCopy?: () => void @@ -24,9 +22,7 @@ export interface UseCopyProps { const DEFAULT_TIMEOUT = 2000 -/** - * A hook for copying text to the clipboard. - */ +/** A hook for copying text to the clipboard. */ export function useCopy(props: UseCopyProps) { const { copyText, onCopy, successToastMessage = true } = props diff --git a/app/gui/src/dashboard/hooks/debounceCallbackHooks.ts b/app/gui/src/dashboard/hooks/debounceCallbackHooks.ts index 720258d50b8..bccc6e0b878 100644 --- a/app/gui/src/dashboard/hooks/debounceCallbackHooks.ts +++ b/app/gui/src/dashboard/hooks/debounceCallbackHooks.ts @@ -8,9 +8,7 @@ import * as React from 'react' import * as callbackHooks from './eventCallbackHooks' import * as unmountEffect from './unmountHooks' -/** - * Wrap a callback into debounce function - */ +/** Wrap a callback into debounce function */ export function useDebouncedCallback unknown>( callback: Fn, deps: React.DependencyList, @@ -83,9 +81,8 @@ export function useDebouncedCallback unknown>( }, [stableCallback, delay, maxWait, ...deps]) } -/** - * - */ -export interface DebouncedFunction unknown> { - (this: ThisParameterType, ...args: Parameters): void -} +/** The type of a wrapped function that has been debounced. */ +export type DebouncedFunction unknown> = ( + this: ThisParameterType, + ...args: Parameters +) => void diff --git a/app/gui/src/dashboard/hooks/debounceStateHooks.ts b/app/gui/src/dashboard/hooks/debounceStateHooks.ts index 4d26d43b3c2..697bd28cc45 100644 --- a/app/gui/src/dashboard/hooks/debounceStateHooks.ts +++ b/app/gui/src/dashboard/hooks/debounceStateHooks.ts @@ -9,9 +9,7 @@ import * as React from 'react' import * as debouncedCallback from './debounceCallbackHooks' import * as eventCallbackHooks from './eventCallbackHooks' -/** - * A hook that returns a stateful value, and a function to update it that will debounce updates. - */ +/** A hook that returns a stateful value, and a function to update it that will debounce updates. */ export function useDebounceState( initialState: S | (() => S), delay: number, diff --git a/app/gui/src/dashboard/hooks/debounceValueHooks.ts b/app/gui/src/dashboard/hooks/debounceValueHooks.ts index 58843ea4d69..bea8c4098ef 100644 --- a/app/gui/src/dashboard/hooks/debounceValueHooks.ts +++ b/app/gui/src/dashboard/hooks/debounceValueHooks.ts @@ -5,9 +5,7 @@ */ import * as debounceState from './debounceStateHooks' -/** - * Debounce a value. - */ +/** Debounce a value. */ export function useDebounceValue(value: T, delay: number, maxWait?: number) { const [debouncedValue, setDebouncedValue] = debounceState.useDebounceState(value, delay, maxWait) diff --git a/app/gui/src/dashboard/hooks/mountHooks.ts b/app/gui/src/dashboard/hooks/mountHooks.ts index 694a54fcb23..8616db4d4e5 100644 --- a/app/gui/src/dashboard/hooks/mountHooks.ts +++ b/app/gui/src/dashboard/hooks/mountHooks.ts @@ -34,9 +34,7 @@ export function useMounted(callback: () => void) { }, [stableCallback]) } -/** - * Returns a function that returns `true` if the component renders for the first time. - */ +/** Returns a function that returns `true` if the component renders for the first time. */ export function useIsFirstRender() { const isFirstMount = useRef(true) const stableCallbackTrue = useEventCallback(() => true) diff --git a/app/gui/src/dashboard/hooks/offlineHooks.ts b/app/gui/src/dashboard/hooks/offlineHooks.ts index 5008a252ab9..81294db7c52 100644 --- a/app/gui/src/dashboard/hooks/offlineHooks.ts +++ b/app/gui/src/dashboard/hooks/offlineHooks.ts @@ -9,9 +9,7 @@ import * as reactQuery from '@tanstack/react-query' import * as eventCallback from '#/hooks/eventCallbackHooks' -/** - * Hook to get the offline status - */ +/** Hook to get the offline status */ export function useOffline() { const isOnline = React.useSyncExternalStore( reactQuery.onlineManager.subscribe.bind(reactQuery.onlineManager), @@ -22,17 +20,13 @@ export function useOffline() { return { isOffline: !isOnline } } -/** - * Props for the {@link useOfflineChange} hook - */ +/** Props for the {@link useOfflineChange} hook */ export interface UseOfflineChangeProps { readonly triggerImmediate?: boolean | 'if-offline' | 'if-online' readonly isDisabled?: boolean } -/** - * Hook to subscribe to online/offline changes - */ +/** Hook to subscribe to online/offline changes */ export function useOfflineChange( callback: (isOffline: boolean) => void, props: UseOfflineChangeProps = {}, diff --git a/app/gui/src/dashboard/hooks/searchParamsStateHooks.ts b/app/gui/src/dashboard/hooks/searchParamsStateHooks.ts index 1ff3f7ba154..a4ba6f139d5 100644 --- a/app/gui/src/dashboard/hooks/searchParamsStateHooks.ts +++ b/app/gui/src/dashboard/hooks/searchParamsStateHooks.ts @@ -18,9 +18,7 @@ import * as safeJsonParse from '#/utilities/safeJsonParse' // === SearchParamsStateReturnType === // =================================== -/** - * The return type of the `useSearchParamsState` hook. - */ +/** The return type of the `useSearchParamsState` hook. */ type SearchParamsStateReturnType = Readonly< [ value: T, @@ -29,9 +27,7 @@ type SearchParamsStateReturnType = Readonly< ] > -/** - * Set options for the `set` function. - */ +/** Set options for the `set` function. */ export interface SearchParamsSetOptions { readonly replace?: boolean } diff --git a/app/gui/src/dashboard/hooks/unmountHooks.ts b/app/gui/src/dashboard/hooks/unmountHooks.ts index 7ec6b687a46..8d128491986 100644 --- a/app/gui/src/dashboard/hooks/unmountHooks.ts +++ b/app/gui/src/dashboard/hooks/unmountHooks.ts @@ -1,13 +1,9 @@ -/** - * @file - */ +/** @file */ import * as React from 'react' import * as eventCallback from './eventCallbackHooks' -/** - * Calls callback when component is unmounted. - */ +/** Calls callback when component is unmounted. */ export function useUnmount(callback: () => void) { // by using `useEventCallback` we can ensure that the callback is stable const callbackEvent = eventCallback.useEventCallback(callback) diff --git a/app/gui/src/dashboard/hooks/useLazyMemoHooks.ts b/app/gui/src/dashboard/hooks/useLazyMemoHooks.ts index 9ad5a4c36b1..646f33ba12e 100644 --- a/app/gui/src/dashboard/hooks/useLazyMemoHooks.ts +++ b/app/gui/src/dashboard/hooks/useLazyMemoHooks.ts @@ -8,9 +8,7 @@ import * as React from 'react' const UNSET_VALUE = Symbol('unset') -/** - * A hook that returns a memoized function that will only be called once - */ +/** A hook that returns a memoized function that will only be called once */ export function useLazyMemoHooks(factory: T | (() => T), deps: React.DependencyList): () => T { return React.useMemo(() => { let cachedValue: T | typeof UNSET_VALUE = UNSET_VALUE diff --git a/app/gui/src/dashboard/layouts/AssetsTable.tsx b/app/gui/src/dashboard/layouts/AssetsTable.tsx index c481699c268..ca653885d08 100644 --- a/app/gui/src/dashboard/layouts/AssetsTable.tsx +++ b/app/gui/src/dashboard/layouts/AssetsTable.tsx @@ -334,9 +334,7 @@ export interface AssetsTableProps { readonly assetManagementApiRef: Ref } -/** - * The API for managing assets in the table. - */ +/** The API for managing assets in the table. */ export interface AssetManagementApi { readonly getAsset: (id: AssetId) => AnyAsset | null readonly setAsset: (id: AssetId, asset: AnyAsset) => void @@ -508,9 +506,7 @@ export default function AssetsTable(props: AssetsTableProps) { }, }) - /** - * Return type of the query function for the listDirectory query. - */ + /** Return type of the query function for the listDirectory query. */ type DirectoryQuery = typeof directories.rootDirectory.data const rootDirectoryContent = directories.rootDirectory.data diff --git a/app/gui/src/dashboard/layouts/Settings/Paywall.tsx b/app/gui/src/dashboard/layouts/Settings/Paywall.tsx index f81c3e5ef45..430dbebcfc6 100644 --- a/app/gui/src/dashboard/layouts/Settings/Paywall.tsx +++ b/app/gui/src/dashboard/layouts/Settings/Paywall.tsx @@ -14,9 +14,7 @@ import * as paywallComponents from '#/components/Paywall' import * as twv from '#/utilities/tailwindVariants' -/** - * Props for a {@link SettingsPaywall}. - */ +/** Props for a {@link SettingsPaywall}. */ export interface SettingsPaywallProps { readonly feature: billingHooks.PaywallFeatureName readonly className?: string | undefined @@ -25,9 +23,7 @@ export interface SettingsPaywallProps { const PAYWALL_LAYOUT_STYLES = twv.tv({ base: 'mt-1' }) -/** - * A layout that shows a paywall for a feature. - */ +/** A layout that shows a paywall for a feature. */ export default function SettingsPaywall(props: SettingsPaywallProps) { const { feature, className, onInteracted } = props diff --git a/app/gui/src/dashboard/layouts/Settings/SetupTwoFaForm.tsx b/app/gui/src/dashboard/layouts/Settings/SetupTwoFaForm.tsx index 31bcc63ca49..32388ad3532 100644 --- a/app/gui/src/dashboard/layouts/Settings/SetupTwoFaForm.tsx +++ b/app/gui/src/dashboard/layouts/Settings/SetupTwoFaForm.tsx @@ -166,9 +166,7 @@ export function SetupTwoFaForm() { } } -/** - * Two Factor Authentication Setup Form. - */ +/** Two Factor Authentication Setup Form. */ function TwoFa() { const { cognito } = useAuth() const { getText } = useText() diff --git a/app/gui/src/dashboard/modals/SetupOrganizationAfterSubscribe.tsx b/app/gui/src/dashboard/modals/SetupOrganizationAfterSubscribe.tsx index b51760aed2c..167c8d51045 100644 --- a/app/gui/src/dashboard/modals/SetupOrganizationAfterSubscribe.tsx +++ b/app/gui/src/dashboard/modals/SetupOrganizationAfterSubscribe.tsx @@ -150,9 +150,7 @@ export function SetupOrganizationAfterSubscribe() { ) } -/** - * Props for the SetOrganizationNameForm component. - */ +/** Props for the SetOrganizationNameForm component. */ export interface SetOrganizationNameFormProps { readonly onSubmit: (name: string) => Promise } @@ -168,9 +166,7 @@ export const SET_ORGANIZATION_NAME_FORM_SCHEMA = (getText: GetText) => .max(ORGANIZATION_NAME_MAX_LENGTH, getText('arbitraryFieldTooLong')), }) -/** - * Form for setting the organization name. - */ +/** Form for setting the organization name. */ export function SetOrganizationNameForm(props: SetOrganizationNameFormProps) { const { onSubmit } = props const { getText } = textProvider.useText() @@ -202,16 +198,12 @@ export function SetOrganizationNameForm(props: SetOrganizationNameFormProps) { ) } -/** - * Props for the CreateUserGroupForm component. - */ +/** Props for the CreateUserGroupForm component. */ export interface CreateUserGroupFormProps { readonly onSubmit: (name: string) => Promise } -/** - * Form for creating a user group. - */ +/** Form for creating a user group. */ export function CreateUserGroupForm(props: CreateUserGroupFormProps) { const { onSubmit } = props const { getText } = textProvider.useText() diff --git a/app/gui/src/dashboard/modules/payments/api/createPaymentMethod.ts b/app/gui/src/dashboard/modules/payments/api/createPaymentMethod.ts index bdda11e1182..bdc26ee1c00 100644 --- a/app/gui/src/dashboard/modules/payments/api/createPaymentMethod.ts +++ b/app/gui/src/dashboard/modules/payments/api/createPaymentMethod.ts @@ -6,17 +6,13 @@ import type { Stripe, StripeCardElement } from '@stripe/stripe-js' import { useMutation } from '@tanstack/react-query' -/** - * Parameters for the `createPaymentMethod` mutation. - */ +/** Parameters for the `createPaymentMethod` mutation. */ export interface CreatePaymentMethodMutationParams { readonly cardElement?: StripeCardElement | null | undefined readonly stripeInstance: Stripe } -/** - * Hook for creating a payment method. - */ +/** Hook for creating a payment method. */ export function useCreatePaymentMethodMutation() { return useMutation({ mutationFn: async (params: CreatePaymentMethodMutationParams) => { diff --git a/app/gui/src/dashboard/modules/payments/api/useSubscriptionPrice.ts b/app/gui/src/dashboard/modules/payments/api/useSubscriptionPrice.ts index 4d4632dcfcb..fa8d2b98356 100644 --- a/app/gui/src/dashboard/modules/payments/api/useSubscriptionPrice.ts +++ b/app/gui/src/dashboard/modules/payments/api/useSubscriptionPrice.ts @@ -1,7 +1,8 @@ /** * @file * - * This file contains the `useSubscriptionPrice` hook that is used to fetch the subscription price based on the provided parameters. + * The `useSubscriptionPrice` hook that is used to fetch the subscription price + * based on the provided parameters. */ import { queryOptions, useQuery } from '@tanstack/react-query' @@ -9,25 +10,21 @@ import type { Plan } from '#/services/Backend' import { DISCOUNT_MULTIPLIER_BY_DURATION, PRICE_BY_PLAN } from '../constants' -/** - * - */ -export interface SubscriptionPriceQueryParams { +/** Options for {@link createSubscriptionPriceQuery}. */ +export interface SubscriptionPriceQueryOptions { readonly plan: Plan readonly seats: number readonly period: number } -/** - * Creates a query to fetch the subscription price based on the provided parameters. - */ -export function createSubscriptionPriceQuery(params: SubscriptionPriceQueryParams) { +/** Creates a query to fetch the subscription price based on the provided parameters. */ +export function createSubscriptionPriceQuery(options: SubscriptionPriceQueryOptions) { return queryOptions({ - queryKey: ['getPrice', params] as const, + queryKey: ['getPrice', options] as const, queryFn: ({ queryKey }) => { const [, { seats, period, plan }] = queryKey - const discountMultiplier = DISCOUNT_MULTIPLIER_BY_DURATION[params.period] ?? 1 + const discountMultiplier = DISCOUNT_MULTIPLIER_BY_DURATION[options.period] ?? 1 const fullPrice = PRICE_BY_PLAN[plan] const price = fullPrice * discountMultiplier const discount = fullPrice - price @@ -43,9 +40,7 @@ export function createSubscriptionPriceQuery(params: SubscriptionPriceQueryParam }) } -/** - * Fetches the subscription price based on the provided parameters. - */ -export function useSubscriptionPrice(params: SubscriptionPriceQueryParams) { +/** Fetches the subscription price based on the provided parameters. */ +export function useSubscriptionPrice(params: SubscriptionPriceQueryOptions) { return useQuery(createSubscriptionPriceQuery(params)) } diff --git a/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodForm.tsx b/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodForm.tsx index 2b9dfa6ccb9..0ff825e05ab 100644 --- a/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodForm.tsx +++ b/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodForm.tsx @@ -40,9 +40,7 @@ export function createAddPaymentMethodFormSchema(z: typeof schema, getText: GetT }) } -/** - * A form for adding a payment method. - */ +/** A form for adding a payment method. */ export function AddPaymentMethodForm< Schema extends ReturnType = ReturnType< typeof createAddPaymentMethodFormSchema diff --git a/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodModal.tsx b/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodModal.tsx index 8ff35bdcf5c..7158be2f185 100644 --- a/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodModal.tsx +++ b/app/gui/src/dashboard/modules/payments/components/AddPaymentMethodModal.tsx @@ -12,18 +12,14 @@ import * as ariaComponents from '#/components/AriaComponents' import { AddPaymentMethodForm } from './AddPaymentMethodForm' import { StripeProvider } from './StripeProvider' -/** - * Props for {@link AddPaymentMethodModal}. - */ +/** Props for {@link AddPaymentMethodModal}. */ export interface AddPaymentMethodModalProps { readonly title: string readonly submitText: string readonly onSubmit: (paymentMethodId: stripeJs.PaymentMethod['id']) => Promise | void } -/** - * A modal for adding a payment method. - */ +/** A modal for adding a payment method. */ export default function AddPaymentMethodModal(props: AddPaymentMethodModalProps) { const { title, onSubmit, submitText } = props diff --git a/app/gui/src/dashboard/modules/payments/components/PlanSelector/PlanSelector.tsx b/app/gui/src/dashboard/modules/payments/components/PlanSelector/PlanSelector.tsx index ecbbddb9962..a336a89ceeb 100644 --- a/app/gui/src/dashboard/modules/payments/components/PlanSelector/PlanSelector.tsx +++ b/app/gui/src/dashboard/modules/payments/components/PlanSelector/PlanSelector.tsx @@ -15,9 +15,7 @@ import { getComponentPerPlan } from './getComponentForPlan' const USER_REFETCH_DELAY_MS = 3_000 const USER_REFETCH_TIMEOUT_MS = 30_000 -/** - * The mutation data for the `onCompleteMutation` mutation. - */ +/** The mutation data for the `onCompleteMutation` mutation. */ interface CreateCheckoutSessionMutation { readonly plan: Plan readonly paymentMethodId: string @@ -25,9 +23,7 @@ interface CreateCheckoutSessionMutation { readonly period: number } -/** - * Props for {@link PlanSelector} - */ +/** Props for {@link PlanSelector} */ export interface PlanSelectorProps extends VariantProps { readonly showFreePlan?: boolean readonly hasTrial?: boolean diff --git a/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/Card.tsx b/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/Card.tsx index ed389b3bcb6..2894df22166 100644 --- a/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/Card.tsx +++ b/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/Card.tsx @@ -15,21 +15,13 @@ import * as ariaComponents from '#/components/AriaComponents' import SvgMask from '#/components/SvgMask' import { tv, type VariantProps } from '#/utilities/tailwindVariants' -/** - * Card props - */ +/** Card props */ export interface CardProps extends React.PropsWithChildren, VariantProps { - /** - * Card title - */ + /** Card title */ readonly title: text.TextId - /** - * Card subtitle - */ + /** Card subtitle */ readonly subtitle: text.TextId - /** - * Card features - */ + /** Card features */ readonly features: string[] readonly pricing?: text.TextId readonly submitButton?: React.ReactNode @@ -79,9 +71,7 @@ export const CARD_STYLES = tv({ }, }) -/** - * Card component - */ +/** Card component */ export function Card(props: CardProps) { const { children, diff --git a/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanFeatures.tsx b/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanFeatures.tsx index b3219327594..94c18e6ca97 100644 --- a/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanFeatures.tsx +++ b/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanFeatures.tsx @@ -8,16 +8,12 @@ import Check from '#/assets/check_mark.svg' import { Text } from '#/components/AriaComponents' import SvgMask from '#/components/SvgMask' -/** - * Props for the PlanFeatures component - */ +/** Props for the PlanFeatures component */ export interface PlanFeaturesProps { readonly features: string[] } -/** - * Render a list of features - */ +/** Render a list of features */ export function PlanFeatures(props: PlanFeaturesProps) { const { features } = props diff --git a/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanSelectorDialog.tsx b/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanSelectorDialog.tsx index 1a1c8fa981c..7bc3af09d08 100644 --- a/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanSelectorDialog.tsx +++ b/app/gui/src/dashboard/modules/payments/components/PlanSelector/components/PlanSelectorDialog.tsx @@ -38,9 +38,7 @@ import { AddPaymentMethodForm, createAddPaymentMethodFormSchema } from '../../Ad import { StripeProvider } from '../../StripeProvider' import { PlanFeatures } from './PlanFeatures' -/** - * Props for {@link PlanSelectorDialog}. - */ +/** Props for {@link PlanSelectorDialog}. */ export interface PlanSelectorDialogProps { readonly plan: Plan readonly planName: string @@ -53,9 +51,7 @@ export interface PlanSelectorDialogProps { interval: number, ) => Promise | void) | undefined - /** - * Whether the user clicked on the trial button. - */ + /** Whether the user clicked on the trial button. */ readonly isTrialing?: boolean } @@ -67,9 +63,7 @@ function billingPeriodToString(getText: GetText, item: number) { ) } -/** - * Dialog that shows the plan details, price, and the payment form. - */ +/** Dialog that shows the plan details, price, and the payment form. */ export function PlanSelectorDialog(props: PlanSelectorDialogProps) { const { title, planName, features, plan, isTrialing = false, onSubmit } = props const { getText, locale } = useText() @@ -228,9 +222,7 @@ export function PlanSelectorDialog(props: PlanSelectorDialogProps) { ) } -/** - * Props for {@link Summary}. - */ +/** Props for {@link Summary}. */ interface SummaryProps { readonly plan: Plan readonly seats: number @@ -240,9 +232,7 @@ interface SummaryProps { readonly isTrialing?: boolean } -/** - * Displays a summary of the plan details and the total price. - */ +/** Displays a summary of the plan details and the total price. */ function Summary(props: SummaryProps) { const { plan, seats, period, formatter, isInvalid = false } = props const { getText } = useText() diff --git a/app/gui/src/dashboard/modules/payments/components/PlanSelector/getComponentForPlan.tsx b/app/gui/src/dashboard/modules/payments/components/PlanSelector/getComponentForPlan.tsx index 3b662581936..5a4fb091b42 100644 --- a/app/gui/src/dashboard/modules/payments/components/PlanSelector/getComponentForPlan.tsx +++ b/app/gui/src/dashboard/modules/payments/components/PlanSelector/getComponentForPlan.tsx @@ -20,9 +20,7 @@ import * as backendModule from '#/services/Backend' import * as constants from '../../constants' import { SubscribeButton, type SubscribeButtonProps } from './components' -/** - * The component for a plan. - */ +/** The component for a plan. */ export interface ComponentForPlan { readonly pricing: text.TextId readonly features: text.TextId diff --git a/app/gui/src/dashboard/modules/payments/components/StripeProvider.tsx b/app/gui/src/dashboard/modules/payments/components/StripeProvider.tsx index 7ecce22ce86..b13dceac431 100644 --- a/app/gui/src/dashboard/modules/payments/components/StripeProvider.tsx +++ b/app/gui/src/dashboard/modules/payments/components/StripeProvider.tsx @@ -12,16 +12,12 @@ import * as stripe from '@stripe/stripe-js/pure' import * as reactQuery from '@tanstack/react-query' import invariant from 'tiny-invariant' -/** - * Props for a {@link StripeProvider}. - */ +/** Props for a {@link StripeProvider}. */ export interface StripeProviderProps { readonly children: React.ReactNode | ((props: StripeProviderRenderProps) => React.ReactNode) } -/** - * Render props for children of a {@link StripeProvider}. - */ +/** Render props for children of a {@link StripeProvider}. */ export interface StripeProviderRenderProps { readonly stripe: stripeTypes.Stripe readonly elements: stripeTypes.StripeElements @@ -49,9 +45,7 @@ export const stripeQuery = reactQuery.queryOptions({ }, }) -/** - * A component that provides a Stripe context. - */ +/** A component that provides a Stripe context. */ export function StripeProvider(props: StripeProviderProps) { const { children } = props @@ -78,9 +72,7 @@ export function StripeProvider(props: StripeProviderProps) { ) } -/** - * Hook that gets the Stripe instance and elements from the Stripe context. - */ +/** Hook that gets the Stripe instance and elements from the Stripe context. */ export function useStripe() { const stripeInstance = stripeReact.useStripe() const elements = stripeReact.useElements() diff --git a/app/gui/src/dashboard/modules/payments/constants.ts b/app/gui/src/dashboard/modules/payments/constants.ts index 2de38179e34..f6fc064abfe 100644 --- a/app/gui/src/dashboard/modules/payments/constants.ts +++ b/app/gui/src/dashboard/modules/payments/constants.ts @@ -1,24 +1,18 @@ -/** - * @file Constants for the subscribe page. - */ +/** @file Constants for the subscribe page. */ import type * as text from 'enso-common/src/text' import * as backendModule from '#/services/Backend' /* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/naming-convention */ -/** - * The text id for the plan name. - */ +/** The text id for the plan name. */ export const PLAN_TO_TEXT_ID: Readonly> = { [backendModule.Plan.free]: 'freePlanName', [backendModule.Plan.solo]: 'soloPlanName', [backendModule.Plan.team]: 'teamPlanName', [backendModule.Plan.enterprise]: 'enterprisePlanName', } satisfies { [Plan in backendModule.Plan]: `${Plan}PlanName` } -/** - * The text id for the plan name. - */ +/** The text id for the plan name. */ export const PLAN_TO_UPGRADE_LABEL_ID: Readonly> = { [backendModule.Plan.free]: 'freePlanUpgradeLabel', [backendModule.Plan.solo]: 'soloPlanUpgradeLabel', diff --git a/app/gui/src/dashboard/pages/authentication/Setup/Setup.tsx b/app/gui/src/dashboard/pages/authentication/Setup/Setup.tsx index 5f4aa414c9f..656b731f9ce 100644 --- a/app/gui/src/dashboard/pages/authentication/Setup/Setup.tsx +++ b/app/gui/src/dashboard/pages/authentication/Setup/Setup.tsx @@ -31,9 +31,7 @@ import { InviteUsersForm } from '#/modals/InviteUsersModal' import { PlanSelector } from '#/modules/payments' import { Plan } from '#/services/Backend' -/** - * Step in the setup process - */ +/** Step in the setup process */ interface Step { readonly title: text.TextId readonly description?: text.TextId @@ -45,9 +43,7 @@ interface Step { readonly ignore?: (context: Context) => boolean } -/** - * Context for the setup process - */ +/** Context for the setup process */ interface Context { readonly session: ReturnType['session'] readonly plan: Plan @@ -351,9 +347,7 @@ const BASE_STEPS: Step[] = [ }, ] -/** - * Setup page - */ +/** Setup page */ export function Setup() { const { getText } = textProvider.useText() const { session } = useAuth() diff --git a/app/gui/src/dashboard/pages/authentication/schemas.ts b/app/gui/src/dashboard/pages/authentication/schemas.ts index 3e806e9f9d3..eabed9dc28d 100644 --- a/app/gui/src/dashboard/pages/authentication/schemas.ts +++ b/app/gui/src/dashboard/pages/authentication/schemas.ts @@ -7,9 +7,7 @@ import type { GetText } from '#/providers/TextProvider' import { PASSWORD_REGEX } from '#/utilities/validation' import { z } from 'zod' -/** - * A schema for validating passwords. - */ +/** A schema for validating passwords. */ export function passwordSchema(getText: GetText) { return ( z @@ -21,9 +19,7 @@ export function passwordSchema(getText: GetText) { ) } -/** - * A schema for validating passwords that match the required pattern. - */ +/** A schema for validating passwords that match the required pattern. */ export function passwordWithPatternSchema(getText: GetText) { return passwordSchema(getText).refine( (password) => PASSWORD_REGEX.test(password), diff --git a/app/gui/src/dashboard/pages/dashboard/Dashboard.tsx b/app/gui/src/dashboard/pages/dashboard/Dashboard.tsx index dca1dc1bcd7..d06d624b5f7 100644 --- a/app/gui/src/dashboard/pages/dashboard/Dashboard.tsx +++ b/app/gui/src/dashboard/pages/dashboard/Dashboard.tsx @@ -103,9 +103,7 @@ export default function Dashboard(props: DashboardProps) { ) } -/** - * Extract proper path from `file://` URL. - */ +/** Extract proper path from `file://` URL. */ function fileURLToPath(url: string): string | null { if (URL.canParse(url)) { const parsed = new URL(url) diff --git a/app/gui/src/dashboard/providers/AuthProvider.tsx b/app/gui/src/dashboard/providers/AuthProvider.tsx index 724ec788fa6..bcb87f3c213 100644 --- a/app/gui/src/dashboard/providers/AuthProvider.tsx +++ b/app/gui/src/dashboard/providers/AuthProvider.tsx @@ -111,9 +111,7 @@ interface AuthContextType { readonly changePassword: (oldPassword: string, newPassword: string) => Promise readonly resetPassword: (email: string, code: string, password: string) => Promise readonly signOut: () => Promise - /** - * @deprecated Never use this function. Prefer particular functions like `setUsername` or `deleteUser`. - */ + /** @deprecated Never use this function. Prefer particular functions like `setUsername` or `deleteUser`. */ readonly setUser: (user: Partial) => void readonly deleteUser: () => Promise readonly restoreUser: () => Promise @@ -141,9 +139,7 @@ const AuthContext = React.createContext(null) // === AuthProvider === // ==================== -/** - * Query to fetch the user's session data from the backend. - */ +/** Query to fetch the user's session data from the backend. */ function createUsersMeQuery( session: cognitoModule.UserSession | null, remoteBackend: RemoteBackend, diff --git a/app/gui/src/dashboard/providers/FeatureFlagsProvider.tsx b/app/gui/src/dashboard/providers/FeatureFlagsProvider.tsx index d028586a202..92762938efd 100644 --- a/app/gui/src/dashboard/providers/FeatureFlagsProvider.tsx +++ b/app/gui/src/dashboard/providers/FeatureFlagsProvider.tsx @@ -14,9 +14,7 @@ import { z } from 'zod' import { createStore, useStore } from 'zustand' declare module '#/utilities/LocalStorage' { - /** - * Local storage data structure. - */ + /** Local storage data structure. */ interface LocalStorageData { readonly featureFlags: z.infer } @@ -31,9 +29,7 @@ export const FEATURE_FLAGS_SCHEMA = z.object({ LocalStorage.registerKey('featureFlags', { schema: FEATURE_FLAGS_SCHEMA }) -/** - * Feature flags store. - */ +/** Feature flags store. */ export interface FeatureFlags { readonly featureFlags: { readonly enableMultitabs: boolean @@ -58,25 +54,19 @@ const flagsStore = createStore((set) => ({ }, })) -/** - * Hook to get all feature flags. - */ +/** Hook to get all feature flags. */ export function useFeatureFlags() { return useStore(flagsStore, (state) => state.featureFlags) } -/** - * Hook to get a specific feature flag. - */ +/** Hook to get a specific feature flag. */ export function useFeatureFlag( key: Key, ): FeatureFlags['featureFlags'][Key] { return useStore(flagsStore, ({ featureFlags }) => featureFlags[key]) } -/** - * Hook to set feature flags. - */ +/** Hook to set feature flags. */ export function useSetFeatureFlags() { return useStore(flagsStore, ({ setFeatureFlags }) => setFeatureFlags) } diff --git a/app/gui/src/dashboard/providers/HttpClientProvider.tsx b/app/gui/src/dashboard/providers/HttpClientProvider.tsx index 6a2cc029aa7..28d5b6d5c79 100644 --- a/app/gui/src/dashboard/providers/HttpClientProvider.tsx +++ b/app/gui/src/dashboard/providers/HttpClientProvider.tsx @@ -11,9 +11,7 @@ import type HttpClient from '#/utilities/HttpClient' const HTTPClientContext = React.createContext(null) -/** - * Props for an {@link HttpClientProvider}. - */ +/** Props for an {@link HttpClientProvider}. */ export interface HttpClientProviderProps extends React.PropsWithChildren { readonly httpClient: HttpClient } @@ -27,9 +25,7 @@ export function HttpClientProvider(props: HttpClientProviderProps) { return {children} } -/** - * Returns the HTTP client. - */ +/** Returns the HTTP client. */ export function useHttpClient() { const httpClient = React.useContext(HTTPClientContext) diff --git a/app/gui/src/dashboard/providers/ModalProvider.tsx b/app/gui/src/dashboard/providers/ModalProvider.tsx index bd49b46428b..11dd98a947a 100644 --- a/app/gui/src/dashboard/providers/ModalProvider.tsx +++ b/app/gui/src/dashboard/providers/ModalProvider.tsx @@ -99,9 +99,7 @@ export function useModal() { // === useModalRef === // =================== -/** - * A React context hook exposing the currently active modal (if one is currently visible) as a ref. - */ +/** A React context hook exposing the currently active modal (if one is currently visible) as a ref. */ export function useModalRef() { const { modalRef } = React.useContext(ModalStaticContext) return { modalRef } as const diff --git a/app/gui/src/dashboard/providers/ProjectsProvider.tsx b/app/gui/src/dashboard/providers/ProjectsProvider.tsx index cc8a717dc48..820762fd7ea 100644 --- a/app/gui/src/dashboard/providers/ProjectsProvider.tsx +++ b/app/gui/src/dashboard/providers/ProjectsProvider.tsx @@ -55,13 +55,9 @@ const PROJECT_SCHEMA = z .readonly() const LAUNCHED_PROJECT_SCHEMA = z.array(PROJECT_SCHEMA).readonly() -/** - * Launched project information. - */ +/** Launched project information. */ export type LaunchedProject = z.infer -/** - * Launched project ID. - */ +/** Launched project ID. */ export type LaunchedProjectId = backendModule.ProjectId LocalStorage.registerKey('launchedProjects', { diff --git a/app/gui/src/dashboard/providers/SessionProvider.tsx b/app/gui/src/dashboard/providers/SessionProvider.tsx index 84427d36224..6e70ee8fa31 100644 --- a/app/gui/src/dashboard/providers/SessionProvider.tsx +++ b/app/gui/src/dashboard/providers/SessionProvider.tsx @@ -58,9 +58,7 @@ export interface SessionProviderProps { const FIVE_MINUTES_MS = 300_000 const SIX_HOURS_MS = 21_600_000 -/** - * Create a query for the user session. - */ +/** Create a query for the user session. */ function createSessionQuery(userSession: (() => Promise) | null) { return reactQuery.queryOptions({ queryKey: ['userSession'], diff --git a/app/gui/src/dashboard/services/LocalBackend.ts b/app/gui/src/dashboard/services/LocalBackend.ts index 4dd7cdf2904..a7377c387c8 100644 --- a/app/gui/src/dashboard/services/LocalBackend.ts +++ b/app/gui/src/dashboard/services/LocalBackend.ts @@ -871,9 +871,7 @@ export default class LocalBackend extends Backend { return this.invalidOperation() } - /** - * Invalid operation. - */ + /** Invalid operation. */ override createCustomerPortalSession() { return this.invalidOperation() } diff --git a/app/gui/src/dashboard/services/ProjectManager.ts b/app/gui/src/dashboard/services/ProjectManager.ts index e554bb134b1..bf766dea99f 100644 --- a/app/gui/src/dashboard/services/ProjectManager.ts +++ b/app/gui/src/dashboard/services/ProjectManager.ts @@ -379,9 +379,7 @@ export default class ProjectManager { this.rootDirectory = this.initialRootDirectory } - /** - * Dispose of the {@link ProjectManager}. - */ + /** Dispose of the {@link ProjectManager}. */ async dispose() { const socket = await this.socketPromise socket.close() diff --git a/app/gui/src/dashboard/services/RemoteBackend.ts b/app/gui/src/dashboard/services/RemoteBackend.ts index 418ce57348b..52de6b68f89 100644 --- a/app/gui/src/dashboard/services/RemoteBackend.ts +++ b/app/gui/src/dashboard/services/RemoteBackend.ts @@ -1249,9 +1249,7 @@ export default class RemoteBackend extends Backend { await download.downloadWithHeaders(url, this.client.defaultHeaders, name) } - /** - * Fetch the URL of the customer portal. - */ + /** Fetch the URL of the customer portal. */ override async createCustomerPortalSession() { const response = await this.post( remoteBackendPaths.getCustomerPortalSessionPath(), diff --git a/app/gui/src/dashboard/services/remoteBackendPaths.ts b/app/gui/src/dashboard/services/remoteBackendPaths.ts index 035e780fe74..c686af11654 100644 --- a/app/gui/src/dashboard/services/remoteBackendPaths.ts +++ b/app/gui/src/dashboard/services/remoteBackendPaths.ts @@ -78,9 +78,7 @@ export const GET_LOG_EVENTS_PATH = 'log_events' /** Relative HTTP path to the "post log event" endpoint of the Cloud backend API. */ export const POST_LOG_EVENT_PATH = 'logs' -/** - * Relative HTTP path to the "get customer portal session" endpoint of the Cloud backend API. - */ +/** Relative HTTP path to the "get customer portal session" endpoint of the Cloud backend API. */ export function getCustomerPortalSessionPath(returnUrl?: string) { const baseUrl = 'payments/customer-portal-sessions/create' diff --git a/app/gui/src/dashboard/utilities/HttpClient.ts b/app/gui/src/dashboard/utilities/HttpClient.ts index 5eda3b79994..cf6eb2b2314 100644 --- a/app/gui/src/dashboard/utilities/HttpClient.ts +++ b/app/gui/src/dashboard/utilities/HttpClient.ts @@ -122,9 +122,7 @@ export default class HttpClient { }) } - /** - * Set the session token to be included in the Authorization header of every request. - */ + /** Set the session token to be included in the Authorization header of every request. */ setSessionToken(token: string) { this.defaultHeaders = { ...this.defaultHeaders, diff --git a/app/gui/src/dashboard/utilities/error.ts b/app/gui/src/dashboard/utilities/error.ts index 86447adb8c9..ba75b259521 100644 --- a/app/gui/src/dashboard/utilities/error.ts +++ b/app/gui/src/dashboard/utilities/error.ts @@ -64,9 +64,7 @@ export function tryGetError(error: MustNotBeKnown): string | null { : null } -/** - * Extracts the `stack` property of a value if it is a string. Intended to be used on {@link Error}s. - */ +/** Extracts the `stack` property of a value if it is a string. Intended to be used on {@link Error}s. */ export function tryGetStack( error: MustNotBeKnown, // eslint-disable-next-line no-restricted-syntax @@ -153,9 +151,7 @@ export function assert(makeValue: () => T | '' | 0 | 0n | false | null | unde } } -/** - * Checks if the given error is a JavaScript execution error. - */ +/** Checks if the given error is a JavaScript execution error. */ export function isJSError(error: unknown): boolean { if (error instanceof TypeError) { return true diff --git a/app/gui/src/dashboard/utilities/inputBindings.ts b/app/gui/src/dashboard/utilities/inputBindings.ts index a0c79c47e3f..01f6e37d3e5 100644 --- a/app/gui/src/dashboard/utilities/inputBindings.ts +++ b/app/gui/src/dashboard/utilities/inputBindings.ts @@ -350,9 +350,7 @@ export type AutocompleteKeybind = { [K in keyof T]: AutocompleteKeybind } diff --git a/app/gui/src/dashboard/utilities/mergeRefs.ts b/app/gui/src/dashboard/utilities/mergeRefs.ts index 5e12a053d6d..b30d8f34a84 100644 --- a/app/gui/src/dashboard/utilities/mergeRefs.ts +++ b/app/gui/src/dashboard/utilities/mergeRefs.ts @@ -33,9 +33,7 @@ export function useMergedRef( new Proxy( { current: null satisfies T | null }, { - /** - * Set the value of the ref object and call all the refs. - */ + /** Set the value of the ref object and call all the refs. */ set( target: { current: null }, p: string | symbol, diff --git a/app/gui/src/dashboard/utilities/tailwindVariants.ts b/app/gui/src/dashboard/utilities/tailwindVariants.ts index aaec9a397b9..08d26f452a5 100644 --- a/app/gui/src/dashboard/utilities/tailwindVariants.ts +++ b/app/gui/src/dashboard/utilities/tailwindVariants.ts @@ -21,9 +21,7 @@ export type ExtractFunction = /** A `tailwind-variants` type, without restrictions on the `extends` key. */ export type TVWithoutExtends = ExtractFunction & Omit -/** - * Props for a component that uses `tailwind-variants`. - */ +/** Props for a component that uses `tailwind-variants`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type VariantProps any> = Omit< TvVariantProps, diff --git a/app/gui/src/project-view/asyncApp.ts b/app/gui/src/project-view/asyncApp.ts index 9cd815915f8..bf60c6c6918 100644 --- a/app/gui/src/project-view/asyncApp.ts +++ b/app/gui/src/project-view/asyncApp.ts @@ -1,8 +1,6 @@ import '@/assets/base.css' -/** - * Load App.vue asynchronously. - */ +/** Load App.vue asynchronously. */ export async function AsyncApp() { const app = await import('@/App.vue') return app diff --git a/app/gui/src/project-view/components/CodeEditor.vue b/app/gui/src/project-view/components/CodeEditor.vue index e37c3f129fa..89577636dba 100644 --- a/app/gui/src/project-view/components/CodeEditor.vue +++ b/app/gui/src/project-view/components/CodeEditor.vue @@ -248,7 +248,7 @@ watch( ) onUnmounted(() => graphStore.moduleSource.unobserve(observeSourceChange)) -function observeSourceChange(textEdits: SourceRangeEdit[], origin: Origin | undefined) { +function observeSourceChange(textEdits: readonly SourceRangeEdit[], origin: Origin | undefined) { // If we received an update from outside the Code Editor while the editor contained uncommitted changes, we cannot // proceed incrementally; we wait for the changes to be merged as Y.Js AST updates, and then set the view to the // resulting code. diff --git a/app/gui/src/project-view/components/ComponentBrowser/ComponentList.vue b/app/gui/src/project-view/components/ComponentBrowser/ComponentList.vue index ef017d6675a..bef468d3a77 100644 --- a/app/gui/src/project-view/components/ComponentBrowser/ComponentList.vue +++ b/app/gui/src/project-view/components/ComponentBrowser/ComponentList.vue @@ -50,9 +50,7 @@ function componentStyle(index: number) { return { transform: `translateY(${componentPos(index)}px)` } } -/** - * Group colors are populated in `GraphEditor`, and for each group in suggestion database a CSS variable is created. - */ +/** Group colors are populated in `GraphEditor`, and for each group in suggestion database a CSS variable is created. */ function componentColor(component: Component): string { return groupColorStyle(tryGetIndex(suggestionDbStore.groups, component.group)) } diff --git a/app/gui/src/project-view/components/ComponentBrowser/component.ts b/app/gui/src/project-view/components/ComponentBrowser/component.ts index f6303699bec..42f6341c886 100644 --- a/app/gui/src/project-view/components/ComponentBrowser/component.ts +++ b/app/gui/src/project-view/components/ComponentBrowser/component.ts @@ -23,9 +23,7 @@ interface ComponentLabel { matchedRanges?: Range[] | undefined } -/** - * A model of component suggestion displayed in the Component Browser. - */ +/** A model of component suggestion displayed in the Component Browser. */ export interface Component extends ComponentLabel { suggestionId: SuggestionId icon: Icon @@ -72,18 +70,14 @@ function formatLabel(labelInfo: ComponentLabelInfo): ComponentLabel { } } -/** - * Suggestion entry with matching information. - */ +/** Suggestion entry with matching information. */ export interface MatchedSuggestion { id: SuggestionId entry: SuggestionEntry match: MatchResult } -/** - * A suggestion comparator. The "lower" suggestion should be first in Component Browser's list. - */ +/** A suggestion comparator. The "lower" suggestion should be first in Component Browser's list. */ export function compareSuggestions(a: MatchedSuggestion, b: MatchedSuggestion): number { const matchCompare = a.match.score - b.match.score if (matchCompare !== 0) return matchCompare @@ -103,9 +97,7 @@ interface ComponentInfo { match: MatchResult } -/** - * Create {@link Component} from information about suggestion and matching. - */ +/** Create {@link Component} from information about suggestion and matching. */ export function makeComponent({ id, entry, match }: ComponentInfo): Component { return { ...formatLabel(labelOfEntry(entry, match)), @@ -115,9 +107,7 @@ export function makeComponent({ id, entry, match }: ComponentInfo): Component { } } -/** - * Create {@link Component} list from filtered suggestions. - */ +/** Create {@link Component} list from filtered suggestions. */ export function makeComponentList(db: SuggestionDb, filtering: Filtering): Component[] { function* matchSuggestions() { for (const [id, entry] of db.entries()) { diff --git a/app/gui/src/project-view/components/ComponentBrowser/placement.ts b/app/gui/src/project-view/components/ComponentBrowser/placement.ts index 5ed5c37b670..36e4c54b625 100644 --- a/app/gui/src/project-view/components/ComponentBrowser/placement.ts +++ b/app/gui/src/project-view/components/ComponentBrowser/placement.ts @@ -16,9 +16,7 @@ const orDefaultSize = (rect: Rect) => { return new Rect(rect.pos, new Vec2(width, height)) } -/** - * A composable with logic related to nodes placement. - */ +/** A composable with logic related to nodes placement. */ export function usePlacement(nodeRects: ToValue>, screenBounds: ToValue) { const gap = themeGap() const environment = (selectedNodeRects: Iterable) => ({ diff --git a/app/gui/src/project-view/components/DocumentationPanel/DocsBreadcrumbs.vue b/app/gui/src/project-view/components/DocumentationPanel/DocsBreadcrumbs.vue index 722d6a263bd..d435fd889d2 100644 --- a/app/gui/src/project-view/components/DocumentationPanel/DocsBreadcrumbs.vue +++ b/app/gui/src/project-view/components/DocumentationPanel/DocsBreadcrumbs.vue @@ -18,9 +18,7 @@ const props = defineProps<{ }>() const emit = defineEmits<{ click: [index: number]; backward: []; forward: [] }>() -/** - * Shrink first and middle elements in the breacrumbs, keeping the original size of others. - */ +/** Shrink first and middle elements in the breacrumbs, keeping the original size of others. */ function shrinkFactor(index: number): number { const middle = Math.floor(props.breadcrumbs.length / 2) return index === middle || index === 0 ? 100 : 0 diff --git a/app/gui/src/project-view/components/DocumentationPanel/history.ts b/app/gui/src/project-view/components/DocumentationPanel/history.ts index c016e3c60d0..6cc3dfcddf0 100644 --- a/app/gui/src/project-view/components/DocumentationPanel/history.ts +++ b/app/gui/src/project-view/components/DocumentationPanel/history.ts @@ -2,9 +2,7 @@ import type { SuggestionId } from '@/stores/suggestionDatabase/entry' import type { ComputedRef, Ref } from 'vue' import { computed, reactive, ref } from 'vue' -/** - * Simple stack for going forward and backward through the history of visited documentation pages - */ +/** Simple stack for going forward and backward through the history of visited documentation pages */ export class HistoryStack { private stack: SuggestionId[] private index: Ref diff --git a/app/gui/src/project-view/components/DocumentationPanel/ir.ts b/app/gui/src/project-view/components/DocumentationPanel/ir.ts index ee9b4a90fea..723e3de24d9 100644 --- a/app/gui/src/project-view/components/DocumentationPanel/ir.ts +++ b/app/gui/src/project-view/components/DocumentationPanel/ir.ts @@ -7,9 +7,7 @@ import type { SuggestionEntryArgument } from 'ydoc-shared/languageServerTypes/su // === Types === -/** - * Intermediate representation of the entries documentation. - */ +/** Intermediate representation of the entries documentation. */ export type Docs = FunctionDocs | TypeDocs | ModuleDocs | LocalDocs | Placeholder @@ -59,9 +57,7 @@ export interface Example { body: Doc.HtmlString } -/** - * Placeholder constructor. - */ +/** Placeholder constructor. */ export function placeholder(text: string): Placeholder { return { kind: 'Placeholder', text } } @@ -99,9 +95,7 @@ function filterSections(sections: Iterable): Sections { // === Lookup === -/** - * The main function for getting documentation page for given entry. - */ +/** The main function for getting documentation page for given entry. */ export function lookupDocumentation(db: SuggestionDb, id: SuggestionId): Docs { const entry = db.get(id) if (!entry) diff --git a/app/gui/src/project-view/components/SizeTransition.vue b/app/gui/src/project-view/components/SizeTransition.vue index 20704b9e013..df98038619e 100644 --- a/app/gui/src/project-view/components/SizeTransition.vue +++ b/app/gui/src/project-view/components/SizeTransition.vue @@ -25,9 +25,7 @@ const props = withDefaults( width?: boolean /** Enable height transition. Implies `overflow-y: clip` during animation. */ height?: boolean - /** - * Compensate for parent grid or flexbox `gap` by animating `margin-left` from negative value. - */ + /** Compensate for parent grid or flexbox `gap` by animating `margin-left` from negative value. */ leftGap?: boolean /** Total animation duration in milliseconds. */ duration?: number diff --git a/app/gui/src/project-view/components/visualizations/GeoMapVisualization.vue b/app/gui/src/project-view/components/visualizations/GeoMapVisualization.vue index 61e3a3919d6..aa7635843f8 100644 --- a/app/gui/src/project-view/components/visualizations/GeoMapVisualization.vue +++ b/app/gui/src/project-view/components/visualizations/GeoMapVisualization.vue @@ -236,9 +236,7 @@ function initDeckGl() { } } -/** - * Reset the internal state of the visualization, discarding all previous data updates. - */ +/** Reset the internal state of the visualization, discarding all previous data updates. */ function resetState() { // We only need to reset the data points as everything else will be overwritten when new data // arrives. @@ -334,9 +332,7 @@ function centerPoint() { return { latitude, longitude, minX, maxX, minY, maxY } } -/** - * Extract the visualization data from a full configuration object. - */ +/** Extract the visualization data from a full configuration object. */ function extractVisualizationDataFromFullConfig(parsedData: RegularData | Layer) { if ('type' in parsedData && parsedData.type === SCATTERPLOT_LAYER && parsedData.data.length) { pushPoints(parsedData.data) @@ -354,9 +350,7 @@ function extractVisualizationDataFromFullConfig(parsedData: RegularData | Layer) dataPoints.value = dataPoints.value } -/** - * Extract the visualization data from a dataframe. - */ +/** Extract the visualization data from a dataframe. */ function extractVisualizationDataFromDataFrame(parsedData: DataFrame) { const newPoints: Location[] = [] for (let i = 0; i < parsedData.df_latitude.length; i += 1) { diff --git a/app/gui/src/project-view/components/visualizations/SQLVisualization.vue b/app/gui/src/project-view/components/visualizations/SQLVisualization.vue index cad71417a05..24e65d0b672 100644 --- a/app/gui/src/project-view/components/visualizations/SQLVisualization.vue +++ b/app/gui/src/project-view/components/visualizations/SQLVisualization.vue @@ -92,9 +92,7 @@ function replaceAlpha(color: RGBA, newAlpha: number) { } } -/** - * Renders HTML for displaying an Enso parameter that is interpolated into the SQL code. - */ +/** Renders HTML for displaying an Enso parameter that is interpolated into the SQL code. */ function renderInterpolationParameter(theme: Theme, param: { enso_type: string; value: string }) { const actualType = param.enso_type let value = param.value @@ -110,9 +108,7 @@ function renderInterpolationParameter(theme: Theme, param: { enso_type: string; return renderRegularInterpolation(value, fgColor, bgColor) } -/** - * A helper that renders the HTML representation of a regular SQL interpolation. - */ +/** A helper that renders the HTML representation of a regular SQL interpolation. */ function renderRegularInterpolation(value: string, fgColor: RGBA, bgColor: RGBA) { let html = `
electron.BrowserWindow) { // Listen for events to open a URL externally in a browser the user trusts. This is used for // OAuth authentication, both for trustworthiness and for convenience (the ability to use the @@ -143,13 +147,13 @@ export function initAuthentication(window: () => electron.BrowserWindow) { fs.writeFile( path.join(credentialsHomePath, credentialsFileName), JSON.stringify({ - /* eslint-disable @typescript-eslint/naming-convention */ + /* eslint-disable @typescript-eslint/naming-convention, camelcase */ client_id: accessTokenPayload.clientId, access_token: accessTokenPayload.accessToken, refresh_token: accessTokenPayload.refreshToken, refresh_url: accessTokenPayload.refreshUrl, expire_at: accessTokenPayload.expireAt, - /* eslint-enable @typescript-eslint/naming-convention */ + /* eslint-enable @typescript-eslint/naming-convention, camelcase */ }), innerError => { if (innerError) { diff --git a/app/ide-desktop/client/src/config.ts b/app/ide-desktop/client/src/config.ts index 8781c74a2cd..14ff6821aad 100644 --- a/app/ide-desktop/client/src/config.ts +++ b/app/ide-desktop/client/src/config.ts @@ -1,5 +1,7 @@ -/** @file Configuration of the application. It extends the web application configuration with - * Electron-specific options. */ +/** + * @file Configuration of the application. It extends the web application configuration with + * Electron-specific options. + */ import chalk from 'chalk' @@ -557,7 +559,7 @@ export const CONFIG = contentConfig.OPTIONS.merge( }), // Please note that this option uses the snake-case naming convention because // Chrome defines it so. - // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase force_high_performance_gpu: new contentConfig.Option({ passToWebApplication: false, primary: false, @@ -566,7 +568,7 @@ export const CONFIG = contentConfig.OPTIONS.merge( }), // Please note that this option uses the snake-case naming convention because // Chrome defines it so. - // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase force_low_power_gpu: new contentConfig.Option({ passToWebApplication: false, primary: false, diff --git a/app/ide-desktop/client/src/configParser.ts b/app/ide-desktop/client/src/configParser.ts index e2d0847d5fe..9f29c29ddbf 100644 --- a/app/ide-desktop/client/src/configParser.ts +++ b/app/ide-desktop/client/src/configParser.ts @@ -37,8 +37,10 @@ const USAGE = `the application from a web-browser, the creation of a window can be suppressed by ` + `entering either '-window=false' or '-no-window'.` -/** Contains information for a category of command line options and the options - * it is comprised of. */ +/** + * Contains information for a category of command line options and the options + * it is comprised of. + */ class Section { description = '' entries: (readonly [cmdOption: string, option: config.Option])[] = [] @@ -51,7 +53,8 @@ interface PrintHelpConfig { readonly helpExtended: boolean } -/** Command line help printer. The `groupsOrdering` parameter specifies the order in which the +/** + * Command line help printer. The `groupsOrdering` parameter specifies the order in which the * option groups should be printed. Groups not specified will be printed in the definition order. * * We use a custom help printer because Yargs has several issues: @@ -60,7 +63,8 @@ interface PrintHelpConfig { * 3. Every option has a `[type`] annotation and there is no API to disable it. * 4. There is no option to print commands with single dash instead of double-dash. * 5. Help coloring is not supported, and they do not want to support it: - * https://github.com/yargs/yargs/issues/251. */ + * https://github.com/yargs/yargs/issues/251. + */ function printHelp(cfg: PrintHelpConfig) { console.log(USAGE) const totalWidth = logger.terminalWidth() ?? DEFAULT_TERMINAL_WIDTH @@ -146,8 +150,10 @@ function printHelp(cfg: PrintHelpConfig) { } } -/** Wrap the text to a specific output width. If a word is longer than the output width, it will be - * split. */ +/** + * Wrap the text to a specific output width. If a word is longer than the output width, it will be + * split. + */ function wordWrap(str: string, width: number): string[] { if (width <= 0) { logger.error(`Cannot perform word wrap. The output width is set to '${width}'.`) @@ -217,8 +223,10 @@ export class ChromeOption { } } -/** Replace `-no-...` with `--no-...`. This is a hotfix for a Yargs bug: - * https://github.com/yargs/yargs-parser/issues/468. */ +/** + * Replace `-no-...` with `--no-...`. This is a hotfix for a Yargs bug: + * https://github.com/yargs/yargs-parser/issues/468. + */ function fixArgvNoPrefix(argv: readonly string[]): readonly string[] { const singleDashPrefix = '-no-' const doubleDashPrefix = '--no-' @@ -237,8 +245,10 @@ interface ArgvAndChromeOptions { readonly chromeOptions: ChromeOption[] } -/** Parse the given list of arguments into two distinct sets: regular arguments and those specific - * to Chrome. */ +/** + * Parse the given list of arguments into two distinct sets: regular arguments and those specific + * to Chrome. + */ function argvAndChromeOptions(processArgs: readonly string[]): ArgvAndChromeOptions { const chromeOptionRegex = /--?chrome.([^=]*)(?:=(.*))?/ const argv = [] diff --git a/app/ide-desktop/client/src/desktopEnvironment.ts b/app/ide-desktop/client/src/desktopEnvironment.ts index b325e0ddac1..eff00c31f44 100644 --- a/app/ide-desktop/client/src/desktopEnvironment.ts +++ b/app/ide-desktop/client/src/desktopEnvironment.ts @@ -1,6 +1,4 @@ -/** - * @file This module contains the logic for the detection of user-specific desktop environment attributes. - */ +/** @file This module contains the logic for the detection of user-specific desktop environment attributes. */ import * as childProcess from 'node:child_process' import * as os from 'node:os' @@ -10,9 +8,7 @@ export const DOCUMENTS = getDocumentsPath() const CHILD_PROCESS_TIMEOUT = 3000 -/** - * Detects path of the user documents directory depending on the operating system. - */ +/** Detects path of the user documents directory depending on the operating system. */ function getDocumentsPath(): string | undefined { if (process.platform === 'linux') { return getLinuxDocumentsPath() @@ -25,18 +21,14 @@ function getDocumentsPath(): string | undefined { } } -/** - * Returns the user documents path on Linux. - */ +/** Returns the user documents path on Linux. */ function getLinuxDocumentsPath(): string { const xdgDocumentsPath = getXdgDocumentsPath() return xdgDocumentsPath ?? path.join(os.homedir(), 'enso') } -/** - * Gets the documents directory from the XDG directory management system. - */ +/** Gets the documents directory from the XDG directory management system. */ function getXdgDocumentsPath(): string | undefined { const out = childProcess.spawnSync('xdg-user-dir', ['DOCUMENTS'], { timeout: CHILD_PROCESS_TIMEOUT, @@ -57,9 +49,7 @@ function getMacOsDocumentsPath(): string { return path.join(os.homedir(), 'Documents') } -/** - * Get the path to the `My Documents` Windows directory. - */ +/** Get the path to the `My Documents` Windows directory. */ function getWindowsDocumentsPath(): string | undefined { const out = childProcess.spawnSync( 'reg', diff --git a/app/ide-desktop/client/src/fileAssociations.ts b/app/ide-desktop/client/src/fileAssociations.ts index bede7f3061b..27cd32ad1d1 100644 --- a/app/ide-desktop/client/src/fileAssociations.ts +++ b/app/ide-desktop/client/src/fileAssociations.ts @@ -1,9 +1,11 @@ -/** @file +/** + * @file * This module provides functionality for handling file opening events in the Enso IDE. * * It includes utilities for determining if a file can be opened, managing the file opening * process, and launching new instances of the IDE when necessary. The module also exports - * constants related to file associations and project handling. */ + * constants related to file associations and project handling. + */ import * as fsSync from 'node:fs' import * as pathModule from 'node:path' import process from 'node:process' @@ -39,13 +41,15 @@ export const SOURCE_FILE_SUFFIX = fileAssociations.SOURCE_FILE_SUFFIX // === Arguments Handling === // ========================== -/** Check if the given list of application startup arguments denotes an attempt to open a file. +/** + * Check if the given list of application startup arguments denotes an attempt to open a file. * * For example, this happens when the user double-clicks on a file in the file explorer and the * application is launched with the file path as an argument. * @param clientArgs - A list of arguments passed to the application, stripped from the initial * executable name and any electron dev mode arguments. - * @returns The path to the file to open, or `null` if no file was specified. */ + * @returns The path to the file to open, or `null` if no file was specified. + */ export function argsDenoteFileOpenAttempt(clientArgs: readonly string[]): string | null { const arg = clientArgs[0] let result: string | null = null @@ -117,9 +121,11 @@ export function onFileOpened(event: electron.Event, path: string): string | null } } -/** Set up the `open-file` event handler that might import a project and invoke the given callback, +/** + * Set up the `open-file` event handler that might import a project and invoke the given callback, * if this IDE instance should load the project. See {@link onFileOpened} for more details. - * @param setProjectToOpen - A function that will be called with the path of the project to open. */ + * @param setProjectToOpen - A function that will be called with the path of the project to open. + */ export function setOpenFileEventHandler(setProjectToOpen: (path: string) => void) { electron.app.on('open-file', (_event, path) => { logger.log(`Opening file '${path}'.`) @@ -149,13 +155,15 @@ export function setOpenFileEventHandler(setProjectToOpen: (path: string) => void }) } -/** Handle the case where IDE is invoked with a file to open. +/** + * Handle the case where IDE is invoked with a file to open. * * Imports project if necessary. Returns the ID of the project to open. In case of an error, * the error message is displayed and the error is re-thrown. * @param openedFile - The path to the file to open. * @returns The ID of the project to open. - * @throws {Error} if the project from the file cannot be opened or imported. */ + * @throws {Error} if the project from the file cannot be opened or imported. + */ export function handleOpenFile(openedFile: string): project.ProjectInfo { try { return project.importProjectFromPath(openedFile) diff --git a/app/ide-desktop/client/src/globals.d.ts b/app/ide-desktop/client/src/globals.d.ts index 05ed085fb59..b9c41639fbc 100644 --- a/app/ide-desktop/client/src/globals.d.ts +++ b/app/ide-desktop/client/src/globals.d.ts @@ -1,8 +1,8 @@ -/** @file Globals defined outside of TypeScript files. +/** + * @file Globals defined outside of TypeScript files. * These are from variables defined at build time, environment variables, - * monkeypatching on `window` and generated code. */ -import type * as common from 'enso-common' - + * monkeypatching on `window` and generated code. + */ // This file is being imported for its types. // eslint-disable-next-line no-restricted-syntax import * as buildJson from './../../build.json' assert { type: 'json' } @@ -25,8 +25,10 @@ interface Enso { // === Backend API === // =================== -/** `window.backendApi` is a context bridge to the main process, when we're running in an - * Electron context. It contains non-authentication-related functionality. */ +/** + * `window.backendApi` is a context bridge to the main process, when we're running in an + * Electron context. It contains non-authentication-related functionality. + */ interface BackendApi { /** Return the ID of the new project. */ readonly importProjectFromPath: ( @@ -40,7 +42,8 @@ interface BackendApi { // === Authentication API === // ========================== -/** `window.authenticationApi` is a context bridge to the main process, when we're running in an +/** + * `window.authenticationApi` is a context bridge to the main process, when we're running in an * Electron context. * * # Safety @@ -48,12 +51,15 @@ interface BackendApi { * We're assuming that the main process has exposed the `authenticationApi` context bridge (see * `lib/client/src/preload.ts` for details), and that it contains the functions defined in this * interface. Our app can't function if these assumptions are not met, so we're disabling the - * TypeScript checks for this interface when we use it. */ + * TypeScript checks for this interface when we use it. + */ interface AuthenticationApi { /** Open a URL in the system browser. */ readonly openUrlInSystemBrowser: (url: string) => void - /** Set the callback to be called when the system browser redirects back to a URL in the app, - * via a deep link. See `setDeepLinkHandler` for details. */ + /** + * Set the callback to be called when the system browser redirects back to a URL in the app, + * via a deep link. See `setDeepLinkHandler` for details. + */ readonly setDeepLinkHandler: (callback: (url: string) => void) => void /** Saves the access token to a file. */ readonly saveAccessToken: (accessToken: dashboard.AccessToken | null) => void @@ -63,8 +69,10 @@ interface AuthenticationApi { // === Navigation API === // ====================== -/** `window.navigationApi` is a context bridge to the main process, when we're running in an - * Electron context. It contains navigation-related functionality. */ +/** + * `window.navigationApi` is a context bridge to the main process, when we're running in an + * Electron context. It contains navigation-related functionality. + */ interface NavigationApi { /** Go back in the navigation history. */ readonly goBack: () => void @@ -115,8 +123,10 @@ interface ProjectInfo { readonly parentDirectory: string } -/** `window.projectManagementApi` exposes functionality related to system events related to - * project management. */ +/** + * `window.projectManagementApi` exposes functionality related to system events related to + * project management. + */ interface ProjectManagementApi { readonly setOpenProjectHandler: (handler: (projectInfo: ProjectInfo) => void) => void } diff --git a/app/ide-desktop/client/src/index.ts b/app/ide-desktop/client/src/index.ts index 6bec836eb5b..866958cd001 100644 --- a/app/ide-desktop/client/src/index.ts +++ b/app/ide-desktop/client/src/index.ts @@ -41,9 +41,7 @@ import * as urlAssociations from '@/urlAssociations' const logger = contentConfig.logger -/** - * Convert path to proper `file://` URL. - */ +/** Convert path to proper `file://` URL. */ function pathToURL(path: string): URL { if (process.platform === 'win32') { return new URL(encodeURI(`file:///${path.replaceAll('\\', '/')}`)) @@ -198,7 +196,7 @@ class App { if (urlToOpen != null) { urlAssociations.handleOpenUrl(urlToOpen) } - } catch (e) { + } catch { // If we failed to open the file, we should enter the usual welcome screen. // The `handleOpenFile` function will have already displayed an error message. } diff --git a/app/ide-desktop/client/src/ipc.ts b/app/ide-desktop/client/src/ipc.ts index 996458bdffa..e0e0b7ef3cb 100644 --- a/app/ide-desktop/client/src/ipc.ts +++ b/app/ide-desktop/client/src/ipc.ts @@ -1,5 +1,7 @@ -/** @file Inter-Process communication configuration of the application. IPC allows the web-view - * content to exchange information with the Electron application. */ +/** + * @file Inter-Process communication configuration of the application. IPC allows the web-view + * content to exchange information with the Electron application. + */ // =============== // === Channel === diff --git a/app/ide-desktop/client/src/log.ts b/app/ide-desktop/client/src/log.ts index 237046f740e..18899fc0b9d 100644 --- a/app/ide-desktop/client/src/log.ts +++ b/app/ide-desktop/client/src/log.ts @@ -1,10 +1,12 @@ -/** @file Logging utilities. +/** + * @file Logging utilities. * * This module includes a special {@link addFileLog function} that adds a new log consumer that * writes to a file. * * This is the primary entry point, though its building blocks are also exported, - * like {@link FileConsumer}. */ + * like {@link FileConsumer}. + */ import * as fsSync from 'node:fs' import * as pathModule from 'node:path' @@ -18,12 +20,14 @@ import * as paths from '@/paths' // === Log File === // ================ -/** Adds a new log consumer that writes to a file. +/** + * Adds a new log consumer that writes to a file. * * The path of the log file is {@link generateUniqueLogFileName automatically generated}. * * The log file is created in the {@link paths.LOGS_DIRECTORY logs directory} - * @returns The full path of the log file. */ + * @returns The full path of the log file. + */ export function addFileLog(): string { const dirname = paths.LOGS_DIRECTORY const filename = generateUniqueLogFileName() @@ -33,8 +37,10 @@ export function addFileLog(): string { return logFilePath } -/** Generate a unique log file name based on the current timestamp. - * @returns The file name log file. */ +/** + * Generate a unique log file name based on the current timestamp. + * @returns The file name log file. + */ export function generateUniqueLogFileName(): string { // Replace ':' with '-' because ':' is not allowed in file names. const timestamp = new Date().toISOString().replace(/:/g, '-') @@ -51,8 +57,10 @@ export class FileConsumer extends linkedDist.log.Consumer { private readonly logFilePath: string private readonly logFileHandle: number - /** Create a log consumer that writes to a file. - * @param logPath - The path of the log file. Must be writeable. */ + /** + * Create a log consumer that writes to a file. + * @param logPath - The path of the log file. Must be writeable. + */ constructor(logPath: string) { super() // Create the directory if it doesn't exist, otherwise fsSync.openSync will fail. @@ -85,8 +93,10 @@ export class FileConsumer extends linkedDist.log.Consumer { this.message('log', '[GROUP START]', ...args) } - /** Start a collapsed log group - for `FileConsumer`, this does the same thing - * as `startGroup`. */ + /** + * Start a collapsed log group - for `FileConsumer`, this does the same thing + * as `startGroup`. + */ override startGroupCollapsed(...args: unknown[]): void { // We don't have a way to collapse groups in the file logger, so we just use the same // function as startGroup. diff --git a/app/ide-desktop/client/src/modules.d.ts b/app/ide-desktop/client/src/modules.d.ts index 0b5d82e9aca..71fe523d848 100644 --- a/app/ide-desktop/client/src/modules.d.ts +++ b/app/ide-desktop/client/src/modules.d.ts @@ -1,6 +1,8 @@ -/** @file Type definitions for modules that currently lack typings on DefinitelyTyped. +/** + * @file Type definitions for modules that currently lack typings on DefinitelyTyped. * - * This file MUST NOT `export {}` so that the modules are visible to other files. */ + * This file MUST NOT `export {}` so that the modules are visible to other files. + */ // Required because this is a build artifact, which does not exist on a clean repository. declare module '*/build.json' { diff --git a/app/ide-desktop/client/src/paths.ts b/app/ide-desktop/client/src/paths.ts index 99abdc3ffb4..f44c72743bb 100644 --- a/app/ide-desktop/client/src/paths.ts +++ b/app/ide-desktop/client/src/paths.ts @@ -11,24 +11,30 @@ import * as paths from '../paths' // === Paths === // ============= -/** The root of the application bundle. +/** + * The root of the application bundle. * * This path is like: * - for packaged application `…/resources/app.asar`; - * - for development `…` (just the directory with `index.js`). */ + * - for development `…` (just the directory with `index.js`). + */ export const APP_PATH = electron.app.getAppPath() -/** The path of the directory in which the log files of IDE are stored. +/** + * The path of the directory in which the log files of IDE are stored. * - * This is based on the Electron `logs` directory, see {@link electron.app.getPath}. */ + * This is based on the Electron `logs` directory, see {@link electron.app.getPath}. + */ export const LOGS_DIRECTORY = electron.app.getPath('logs') /** The application assets, all files bundled with it. */ export const ASSETS_PATH = path.join(APP_PATH, 'assets') -/** Path to the `resources` folder. +/** + * Path to the `resources` folder. * - * Contains other app resources, including binaries, such a project manager. */ + * Contains other app resources, including binaries, such a project manager. + */ export const RESOURCES_PATH = electronIsDev ? APP_PATH : path.join(APP_PATH, '..') /** Project manager binary path. */ diff --git a/app/ide-desktop/client/src/preload.ts b/app/ide-desktop/client/src/preload.ts index 7ddd05ae959..7243477b82c 100644 --- a/app/ide-desktop/client/src/preload.ts +++ b/app/ide-desktop/client/src/preload.ts @@ -16,7 +16,7 @@ import type * as projectManagement from '@/projectManagement' // esbuild, we have to manually use "require". Switch this to an import once new electron version // actually honours ".mjs" files for sandboxed preloading (this will likely become an error at that time). // https://www.electronjs.org/fr/docs/latest/tutorial/esm#sandboxed-preload-scripts-cant-use-esm-imports -// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports +// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const electron = require('electron') // ================= diff --git a/app/ide-desktop/client/src/projectManager.ts b/app/ide-desktop/client/src/projectManager.ts index deb942d0bd4..14305d4023c 100644 --- a/app/ide-desktop/client/src/projectManager.ts +++ b/app/ide-desktop/client/src/projectManager.ts @@ -17,8 +17,10 @@ const execFile = util.promisify(childProcess.execFile) // === Project Manager === // ======================= -/** Return the Project Manager path. - * @throws If the Project Manager path is invalid. */ +/** + * Return the Project Manager path. + * @throws If the Project Manager path is invalid. + */ export function pathOrPanic(args: config.Args): string { const binPath = args.groups.engine.options.projectManagerPath.value const binExists = fsSync.existsSync(binPath) @@ -35,11 +37,13 @@ async function exec(args: config.Args, processArgs: string[], env?: NodeJS.Proce return await execFile(binPath, processArgs, { env }) } -/** Spawn the Project Manager process. +/** + * Spawn the Project Manager process. * * The standard output and error handles will be redirected to the output and error handles of the * Electron app. Input is piped to this process, so it will not be closed until this process - * finishes. */ + * finishes. + */ export function spawn( args: config.Args, processArgs: string[], diff --git a/app/ide-desktop/client/src/security.ts b/app/ide-desktop/client/src/security.ts index f4e219af95d..bdb7c2ea144 100644 --- a/app/ide-desktop/client/src/security.ts +++ b/app/ide-desktop/client/src/security.ts @@ -1,5 +1,7 @@ -/** @file Security configuration of Electron. Most of the options are based on the official security - * guide: https://www.electronjs.org/docs/latest/tutorial/security. */ +/** + * @file Security configuration of Electron. Most of the options are based on the official security + * guide: https://www.electronjs.org/docs/latest/tutorial/security. + */ import * as electron from 'electron' @@ -36,8 +38,10 @@ const WEBVIEW_URL_WHITELIST: string[] = [] // === Utils === // ============= -/** Secure the web preferences of a new window. It deletes potentially unsecure options, making them - * revert to secure defaults. */ +/** + * Secure the web preferences of a new window. It deletes potentially unsecure options, making them + * revert to secure defaults. + */ export function secureWebPreferences(webPreferences: electron.WebPreferences) { delete webPreferences.preload delete webPreferences.nodeIntegration @@ -54,16 +58,20 @@ export function secureWebPreferences(webPreferences: electron.WebPreferences) { // === Security === // ================ -/** Enabling sandbox globally. Follow the link to learn more: - * https://www.electronjs.org/docs/latest/tutorial/sandbox. */ +/** + * Enabling sandbox globally. Follow the link to learn more: + * https://www.electronjs.org/docs/latest/tutorial/sandbox. + */ function enableGlobalSandbox() { electron.app.enableSandbox() } -/** By default, Electron will automatically approve all permission requests unless the developer has +/** + * By default, Electron will automatically approve all permission requests unless the developer has * manually configured a custom handler. While a solid default, security-conscious developers might * want to assume the very opposite. Follow the link to learn more: - * https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content. */ + * https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content. + */ function rejectPermissionRequests() { void electron.app.whenReady().then(() => { electron.session.defaultSession.setPermissionRequestHandler((_webContents, permission) => { @@ -72,7 +80,8 @@ function rejectPermissionRequests() { }) } -/** This Electron app is configured with extra CORS headers. Those headers are added because they +/** + * This Electron app is configured with extra CORS headers. Those headers are added because they * increase security and enable higher resolution for `performance.now()` timers. However, one of * these headers (i.e., `Cross-Origin-Embedder-Policy: require-corp`), breaks the Stripe.js library. * This is because Stripe.js does not provide a `Cross-Origin-Resource-Policy` header or @@ -89,7 +98,8 @@ function rejectPermissionRequests() { * * The missing headers are added more generally to all resources hosted at `https://js.stripe.com`. * This is because the resources are fingerprinted and the fingerprint changes every time the - * resources are updated. Additionally, Stripe.js may choose to add more resources in the future. */ + * resources are updated. Additionally, Stripe.js may choose to add more resources in the future. + */ function addMissingCorsHeaders() { void electron.app.whenReady().then(() => { electron.session.defaultSession.webRequest.onHeadersReceived((details, callback) => { @@ -103,12 +113,14 @@ function addMissingCorsHeaders() { }) } -/** A WebView created in a renderer process that does not have Node.js integration enabled will not +/** + * A WebView created in a renderer process that does not have Node.js integration enabled will not * be able to enable integration itself. However, a WebView will always create an independent * renderer process with its own webPreferences. It is a good idea to control the creation of new * tags from the main process and to verify that their webPreferences do not disable * security features. Follow the link to learn more: - * https://www.electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation. */ + * https://www.electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation. + */ function limitWebViewCreation() { electron.app.on('web-contents-created', (_event, contents) => { contents.on('will-attach-webview', (event, webPreferences, params) => { @@ -121,10 +133,12 @@ function limitWebViewCreation() { }) } -/** Navigation is a common attack vector. If an attacker can convince your app to navigate away from +/** + * Navigation is a common attack vector. If an attacker can convince your app to navigate away from * its current page, they can possibly force your app to open web sites on the Internet. Follow the * link to learn more: - * https://www.electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation. */ + * https://www.electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation. + */ function preventNavigation() { let lastFocusedWindow = electron.BrowserWindow.getFocusedWindow() electron.app.on('browser-window-focus', () => { @@ -146,11 +160,13 @@ function preventNavigation() { }) } -/** Much like navigation, the creation of new webContents is a common attack vector. Attackers +/** + * Much like navigation, the creation of new webContents is a common attack vector. Attackers * attempt to convince your app to create new windows, frames, or other renderer processes with * more privileges than they had before or with pages opened that they couldn't open before. * Follow the link to learn more: - * https://www.electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows. */ + * https://www.electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows. + */ function disableNewWindowsCreation() { electron.app.on('web-contents-created', (_event, contents) => { contents.setWindowOpenHandler(details => { diff --git a/app/ide-desktop/client/src/server.ts b/app/ide-desktop/client/src/server.ts index 7556ead6385..14c7c183631 100644 --- a/app/ide-desktop/client/src/server.ts +++ b/app/ide-desktop/client/src/server.ts @@ -75,8 +75,10 @@ export class Config { // === Port Finder === // =================== -/** Determine the initial available communication endpoint, starting from the specified port, - * to provide file hosting services. */ +/** + * Determine the initial available communication endpoint, starting from the specified port, + * to provide file hosting services. + */ async function findPort(port: number): Promise { return await portfinder.getPortPromise({ port, startPort: port, stopPort: port + 4 }) } @@ -85,10 +87,12 @@ async function findPort(port: number): Promise { // === Server === // ============== -/** A simple server implementation. +/** + * A simple server implementation. * * Initially it was based on `union`, but later we migrated to `create-servers`. - * Read this topic to learn why: https://github.com/http-party/http-server/issues/483 */ + * Read this topic to learn why: https://github.com/http-party/http-server/issues/483 + */ export class Server { projectsRootDirectory: string devServer?: vite.ViteDevServer @@ -168,9 +172,11 @@ export class Server { }) } - /** Respond to an incoming request. + /** + * Respond to an incoming request. * @throws {Error} when passing invalid JSON to - * `/api/run-project-manager-command?cli-arguments=`. */ + * `/api/run-project-manager-command?cli-arguments=`. + */ process(request: http.IncomingMessage, response: http.ServerResponse) { const requestUrl = request.url const requestPath = requestUrl?.split('?')[0]?.split('#')[0] diff --git a/app/ide-desktop/client/src/urlAssociations.ts b/app/ide-desktop/client/src/urlAssociations.ts index 43fe0a8a18b..34e1ff1f90d 100644 --- a/app/ide-desktop/client/src/urlAssociations.ts +++ b/app/ide-desktop/client/src/urlAssociations.ts @@ -13,13 +13,15 @@ const logger = contentConfig.logger // === Protocol Association === // ============================ -/** Register the application as a handler for our [deep link scheme]{@link common.DEEP_LINK_SCHEME}. +/** + * Register the application as a handler for our [deep link scheme]{@link common.DEEP_LINK_SCHEME}. * * This method is no-op when used under the Electron dev mode, as it requires special handling to * set up the process. * * It is also no-op on macOS, as the OS handles the URL opening by passing the `open-url` event to - * the application, thanks to the information baked in our application by `electron-builder`. */ + * the application, thanks to the information baked in our application by `electron-builder`. + */ export function registerAssociations() { if (!electron.app.isDefaultProtocolClient(common.DEEP_LINK_SCHEME)) { if (electronIsDev) { @@ -40,14 +42,16 @@ export function registerAssociations() { // === URL handling === // ==================== -/** Check if the given list of application startup arguments denotes an attempt to open a URL. +/** + * Check if the given list of application startup arguments denotes an attempt to open a URL. * * For example, this happens on Windows when the browser redirects user using our * [deep link scheme]{@link common.DEEP_LINK_SCHEME}. On macOS this is not used, as the OS * handles the URL opening by passing the `open-url` event to the application. * @param clientArgs - A list of arguments passed to the application, stripped from the initial * executable name and any electron dev mode arguments. - * @returns The URL to open, or `null` if no file was specified. */ + * @returns The URL to open, or `null` if no file was specified. + */ export function argsDenoteUrlOpenAttempt(clientArgs: readonly string[]): URL | null { const arg = clientArgs[0] let result: URL | null = null @@ -69,22 +73,26 @@ export function argsDenoteUrlOpenAttempt(clientArgs: readonly string[]): URL | n let initialUrl: URL | null = null -/** Handle the case where IDE is invoked with a URL to open. +/** + * Handle the case where IDE is invoked with a URL to open. * * This happens on Windows when the browser redirects user using the deep link scheme. - * @param openedUrl - The URL to open. */ + * @param openedUrl - The URL to open. + */ export function handleOpenUrl(openedUrl: URL) { logger.log(`Opening URL '${openedUrl.toString()}'.`) // We must wait for the application to be ready and then send the URL to the renderer process. initialUrl = openedUrl } -/** Register the callback that will be called when the application is requested to open a URL. +/** + * Register the callback that will be called when the application is requested to open a URL. * * This method serves to unify the url handling between macOS and Windows. On macOS, the OS * handles the URL opening by passing the `open-url` event to the application. On Windows, a * new instance of the application is started and the URL is passed as a command line argument. - * @param callback - The callback to call when the application is requested to open a URL. */ + * @param callback - The callback to call when the application is requested to open a URL. + */ export function registerUrlCallback(callback: (url: URL) => void) { if (initialUrl != null) { logger.log(`Got URL from command line: '${initialUrl.toString()}'.`) diff --git a/app/ide-desktop/client/tasks/computeHashes.mjs b/app/ide-desktop/client/tasks/computeHashes.mjs index f0797dfb7ee..69147dfa311 100644 --- a/app/ide-desktop/client/tasks/computeHashes.mjs +++ b/app/ide-desktop/client/tasks/computeHashes.mjs @@ -7,22 +7,25 @@ import * as pathModule from 'node:path' // ================= // === Constants === // ================= -/** @typedef {"md5" | "sha1" | "sha256"} ChecksumType */ const CHECKSUM_TYPE = 'sha256' // ================ // === Checksum === // ================ -/** The `type` argument can be one of `md5`, `sha1`, or `sha256`. +/** + * The `type` argument can be one of `md5`, `sha1`, or `sha256`. * @param {string} path - Path to the file. * @param {ChecksumType} type - The checksum algorithm to use. - * @returns {Promise} A promise that resolves to the checksum. */ + * @returns {Promise} A promise that resolves to the checksum. + */ function getChecksum(path, type) { return new Promise( // This JSDoc annotation is required for correct types that are also type-safe. - /** Promise handler resolving to the file's checksum. - * @param {(value: string) => void} resolve - Fulfill the promise with the given value. */ + /** + * Promise handler resolving to the file's checksum. + * @param {(value: string) => void} resolve - Fulfill the promise with the given value. + */ (resolve, reject) => { const hash = cryptoModule.createHash(type) const input = fs.createReadStream(path) @@ -37,18 +40,22 @@ function getChecksum(path, type) { ) } -/** Based on https://stackoverflow.com/a/57371333. +/** + * Based on https://stackoverflow.com/a/57371333. * @param {string} file - The path to the file. * @param {string} extension - The new extension of the file. - * @returns A path with the new exension. */ + * @returns A path with the new exension. + */ function changeExtension(file, extension) { const basename = pathModule.basename(file, pathModule.extname(file)) return pathModule.join(pathModule.dirname(file), `${basename}.${extension}`) } -/** Write the file checksum to the provided path. +/** + * Write the file checksum to the provided path. * @param {string} path - The path to the file. - * @param {ChecksumType} type - The checksum algorithm to use. */ + * @param {ChecksumType} type - The checksum algorithm to use. + */ async function writeFileChecksum(path, type) { const checksum = await getChecksum(path, type) const targetPath = changeExtension(path, type) @@ -60,7 +67,8 @@ async function writeFileChecksum(path, type) { // === Callback === // ================ -/** Generates checksums for all build artifacts. +/** + * Generates checksums for all build artifacts. * @param {import('electron-builder').BuildResult} context - Build information. * @returns {Promise} afterAllArtifactBuild hook result. */ diff --git a/app/ide-desktop/client/tasks/signArchivesMacOs.ts b/app/ide-desktop/client/tasks/signArchivesMacOs.ts index 77f5cda7451..0e6fb9479db 100644 --- a/app/ide-desktop/client/tasks/signArchivesMacOs.ts +++ b/app/ide-desktop/client/tasks/signArchivesMacOs.ts @@ -1,4 +1,5 @@ -/** @file This script signs the content of all archives that we have for macOS. +/** + * @file This script signs the content of all archives that we have for macOS. * For this to work this needs to run on macOS with `codesign`, and a JDK installed. * `codesign` is needed to sign the files, while the JDK is needed for correct packing * and unpacking of java archives. @@ -11,7 +12,8 @@ * This code is based on https://github.com/electron/electron-osx-sign/pull/231 * but our use-case is unlikely to be supported by `electron-osx-sign` * as it adds a java toolchain as additional dependency. - * This script should be removed once the engine is signed. */ + * This script should be removed once the engine is signed. + */ import * as childProcess from 'node:child_process' import * as fs from 'node:fs/promises' @@ -138,8 +140,10 @@ async function ensoPackageSignables(resourcesDir: string): Promise { /** Information we need to sign a given binary. */ interface SigningContext { - /** A digital identity that is stored in a keychain that is on the calling user's keychain - * search list. We rely on this already being set up by the Electron Builder. */ + /** + * A digital identity that is stored in a keychain that is on the calling user's keychain + * search list. We rely on this already being set up by the Electron Builder. + */ readonly identity: string /** Path to the entitlements file. */ readonly entitlements: string @@ -160,9 +164,11 @@ function run(cmd: string, args: string[], cwd?: string) { return childProcess.execFileSync(cmd, args, { cwd }).toString() } -/** Archive with some binaries that we want to sign. +/** + * Archive with some binaries that we want to sign. * - * Can be either a zip or a jar file. */ + * Can be either a zip or a jar file. + */ class ArchiveToSign implements Signable { /** Looks up for archives to sign using the given path patterns. */ static lookupMany = lookupManyHelper(ArchiveToSign.lookup.bind(this)) @@ -171,8 +177,10 @@ class ArchiveToSign implements Signable { constructor( /** An absolute path to the archive. */ public path: string, - /** A list of patterns for files to sign inside the archive. - * Relative to the root of the archive. */ + /** + * A list of patterns for files to sign inside the archive. + * Relative to the root of the archive. + */ public binaries: glob.Pattern[], ) {} @@ -181,8 +189,10 @@ class ArchiveToSign implements Signable { return lookupHelper(path => new ArchiveToSign(path, binaries))(base, pattern) } - /** Sign content of an archive. This function extracts the archive, signs the required files, - * re-packages the archive and replaces the original. */ + /** + * Sign content of an archive. This function extracts the archive, signs the required files, + * re-packages the archive and replaces the original. + */ async sign(context: SigningContext) { console.log(`Signing archive ${this.path}`) const archiveName = pathModule.basename(this.path) @@ -268,10 +278,12 @@ class BinaryToSign implements Signable { // === Discovering Signables. === // ============================== -/** Helper used to concisely define patterns for an archive to sign. +/** + * Helper used to concisely define patterns for an archive to sign. * * Consists of pattern of the archive path - * and set of patterns for files to sign inside the archive. */ + * and set of patterns for files to sign inside the archive. + */ type ArchivePattern = [glob.Pattern, glob.Pattern[]] /** Like `glob` but returns absolute paths by default. */ @@ -280,8 +292,10 @@ async function globAbsolute(pattern: glob.Pattern, options?: glob.Options): Prom return paths } -/** Glob patterns relative to a given base directory. The base directory is allowed to be a pattern - * as well. */ +/** + * Glob patterns relative to a given base directory. The base directory is allowed to be a pattern + * as well. + */ async function globAbsoluteIn( base: glob.Pattern, pattern: glob.Pattern, diff --git a/app/ide-desktop/client/tests/setup.ts b/app/ide-desktop/client/tests/setup.ts index 44814769222..c836bdb2bca 100644 --- a/app/ide-desktop/client/tests/setup.ts +++ b/app/ide-desktop/client/tests/setup.ts @@ -18,7 +18,7 @@ export default function setup() { try { fs.accessSync(path, fs.constants.X_OK) return true - } catch (err) { + } catch { return false } }) diff --git a/app/ide-desktop/client/watch.ts b/app/ide-desktop/client/watch.ts index f09c2376030..93700ffa2ac 100644 --- a/app/ide-desktop/client/watch.ts +++ b/app/ide-desktop/client/watch.ts @@ -1,11 +1,13 @@ -/** @file This script is for watching the whole IDE and spawning the electron process. +/** + * @file This script is for watching the whole IDE and spawning the electron process. * * It sets up watchers for the client and content, and spawns the electron process with the IDE. * The spawned electron process can then use its refresh capability to pull the latest changes * from the watchers. * * If the electron app is closed, the script will restart it, allowing to test the IDE setup. - * To stop, use Ctrl+C. */ + * To stop, use Ctrl+C. + */ import * as childProcess from 'node:child_process' import * as fs from 'node:fs/promises' import * as path from 'node:path' @@ -107,9 +109,7 @@ process.on('SIGINT', () => { }) }) -/** - * Starts the electron process with the IDE. - */ +/** Starts the electron process with the IDE. */ function startElectronProcess() { console.log('Spawning Electron process.') diff --git a/app/ide-desktop/icons/package.json b/app/ide-desktop/icons/package.json index 79da00fded2..c076c128834 100644 --- a/app/ide-desktop/icons/package.json +++ b/app/ide-desktop/icons/package.json @@ -15,7 +15,8 @@ "url": "https://github.com/enso-org/enso/issues" }, "scripts": { - "build": "node src/index.js" + "build": "node src/index.js", + "lint": "eslint . --max-warnings=0" }, "devDependencies": { "sharp": "^0.31.2", diff --git a/app/ide-desktop/icons/src/index.js b/app/ide-desktop/icons/src/index.js index f3844d0603b..3c6d16b50f6 100644 --- a/app/ide-desktop/icons/src/index.js +++ b/app/ide-desktop/icons/src/index.js @@ -6,6 +6,7 @@ import * as fsSync from 'node:fs' import * as fs from 'node:fs/promises' import * as os from 'node:os' import * as path from 'node:path' +import * as process from 'node:process' import * as url from 'node:url' import sharp from 'sharp' diff --git a/app/ydoc-server-nodejs/package.json b/app/ydoc-server-nodejs/package.json index 64459d99637..f8e47638826 100644 --- a/app/ydoc-server-nodejs/package.json +++ b/app/ydoc-server-nodejs/package.json @@ -12,7 +12,7 @@ "compile": "node ./build.mjs build", "start": "node ./dist/main.mjs", "dev:watch": "node ./build.mjs watch", - "lint": "eslint .", + "lint": "eslint . --max-warnings=0", "format": "prettier --version && prettier --write src/ && eslint . --fix" }, "dependencies": { diff --git a/app/ydoc-server-polyglot/package.json b/app/ydoc-server-polyglot/package.json index f8fe1098860..3e1a123f85c 100644 --- a/app/ydoc-server-polyglot/package.json +++ b/app/ydoc-server-polyglot/package.json @@ -11,7 +11,8 @@ "scripts": { "compile": "node ./build.mjs build", "start": "node ./dist/main.cjs", - "dev:watch": "node ./build.mjs watch" + "dev:watch": "node ./build.mjs watch", + "lint": "eslint . --max-warnings=0" }, "dependencies": { "ydoc-shared": "workspace:*", diff --git a/app/ydoc-server-polyglot/src/ffiPolyglot.ts b/app/ydoc-server-polyglot/src/ffiPolyglot.ts index f231fe7e82b..d9956592ee0 100644 --- a/app/ydoc-server-polyglot/src/ffiPolyglot.ts +++ b/app/ydoc-server-polyglot/src/ffiPolyglot.ts @@ -2,7 +2,6 @@ * @file This file is used as rust ffi interface for building the polyglot ydoc server. * All the exported methods are provided by the ydoc server implementation. * The interface should be kept in sync with Rust ffi interface {@link ydoc-shared/src/ast/ffi}. - * * @module ffiPolyglot */ diff --git a/app/ydoc-server-polyglot/src/polyglot.d.ts b/app/ydoc-server-polyglot/src/polyglot.d.ts index ca6591b9cff..69baf887d7a 100644 --- a/app/ydoc-server-polyglot/src/polyglot.d.ts +++ b/app/ydoc-server-polyglot/src/polyglot.d.ts @@ -1,6 +1,4 @@ -/** - * @file Type declarations for environment provided in polyglot JVM runtime. - */ +/** @file Type declarations for environment provided in polyglot JVM runtime. */ declare class WebSocketServer { constructor(config: any) diff --git a/app/ydoc-server/package.json b/app/ydoc-server/package.json index d51066ee7ab..a4d51c9860b 100644 --- a/app/ydoc-server/package.json +++ b/app/ydoc-server/package.json @@ -11,7 +11,8 @@ "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc", - "compile": "tsc" + "compile": "tsc", + "lint": "eslint . --max-warnings=0" }, "exports": { ".": { diff --git a/app/ydoc-server/src/__tests__/edits.test.ts b/app/ydoc-server/src/__tests__/edits.test.ts index 8ec5eb93fff..8b41a64cb3b 100644 --- a/app/ydoc-server/src/__tests__/edits.test.ts +++ b/app/ydoc-server/src/__tests__/edits.test.ts @@ -7,7 +7,8 @@ import { applyDiffAsTextEdits, stupidFastDiff } from '../edits' // === Test utilities === // ====================== -/** Apply text edits intended for language server to a given starting text. Used for verification +/** + * Apply text edits intended for language server to a given starting text. Used for verification * during testing if generated edits were correct. This is intentionally a very simple, not * performant implementation. */ diff --git a/app/ydoc-server/src/auth.ts b/app/ydoc-server/src/auth.ts index fdb2daeb8c3..6dd20428103 100644 --- a/app/ydoc-server/src/auth.ts +++ b/app/ydoc-server/src/auth.ts @@ -1,6 +1,4 @@ -/** - * @file Utility methods for ydoc server authentication. - */ +/** @file Utility methods for ydoc server authentication. */ export type ConnectionData = { lsUrl: string @@ -10,6 +8,7 @@ export type ConnectionData = { const docNameRegex = /^[a-z0-9/-]+$/i +/** Extract the document name from the path name extracted from the connection. */ export function docName(pathname: string) { const prefix = '/project/' if (pathname != null && pathname.startsWith(prefix)) { diff --git a/app/ydoc-server/src/edits.ts b/app/ydoc-server/src/edits.ts index ef32f656e10..8274e0412da 100644 --- a/app/ydoc-server/src/edits.ts +++ b/app/ydoc-server/src/edits.ts @@ -41,6 +41,7 @@ interface AppliedUpdates { newMetadata: fileFormat.IdeMetadata['node'] | undefined } +/** Return an object containing updated versions of relevant fields, given an update payload. */ export function applyDocumentUpdates( doc: ModuleDoc, synced: EnsoFileParts, @@ -122,6 +123,10 @@ function translateVisualizationToFile( } } +/** + * Convert from the serialized file representation of visualization metadata + * to the internal representation. + */ export function translateVisualizationFromFile( vis: fileFormat.VisualizationMetadata, ): VisualizationMetadata | undefined { @@ -177,6 +182,7 @@ export function stupidFastDiff(oldString: string, newString: string): diff.Diff[ .concat(commonSuffix ? [[0, commonSuffix]] : []) } +/** Return a list of text edits describing how to turn one string into another. */ export function applyDiffAsTextEdits( lineOffset: number, oldString: string, @@ -230,6 +236,7 @@ export function applyDiffAsTextEdits( return edits } +/** Pretty print a code diff for display in the terminal using ANSI escapes to control text colors. */ export function prettyPrintDiff(from: string, to: string): string { const colReset = '\x1b[0m' const colRed = '\x1b[31m' diff --git a/app/ydoc-server/src/fileFormat.ts b/app/ydoc-server/src/fileFormat.ts index 5a5118aaba4..f6d24bf51e6 100644 --- a/app/ydoc-server/src/fileFormat.ts +++ b/app/ydoc-server/src/fileFormat.ts @@ -109,18 +109,20 @@ export function tryParseMetadataOrFallback(metadataJson: string | undefined | nu return metadata.parse(parsedMeta) } +/** Return a parsed {@link IdMap} from a JSON string, or a default value if parsing failed. */ export function tryParseIdMapOrFallback(idMapJson: string | undefined | null): IdMap { if (idMapJson == null) return [] const parsedIdMap = tryParseJson(idMapJson) return idMap.parse(parsedIdMap) } +/** Parse a JSON string, or return `null` if parsing failed instead of throwing an error. */ function tryParseJson(jsonString: string) { try { return json.parse(jsonString) - } catch (e) { + } catch (error) { console.error('Failed to parse metadata JSON:') - console.error(e) + console.error(error) return null } } diff --git a/app/ydoc-server/src/index.ts b/app/ydoc-server/src/index.ts index 1d9900a6f7f..0defa40ab24 100644 --- a/app/ydoc-server/src/index.ts +++ b/app/ydoc-server/src/index.ts @@ -19,9 +19,7 @@ import { setupGatewayClient } from './ydoc' export { deserializeIdMap, docName, setupGatewayClient } -/** - * @param customLogger Optional external logger to use for all debug logs. - */ +/** @param customLogger Optional external logger to use for all debug logs. */ export function configureAllDebugLogs( forceEnable: boolean, customLogger?: (...args: any[]) => any, @@ -33,6 +31,7 @@ export function configureAllDebugLogs( } } +/** Create a WebSocket server to host the YDoc coordinating server. */ export async function createGatewayServer( httpServer: Server | Http2SecureServer, overrideLanguageServerUrl?: string, diff --git a/app/ydoc-server/src/languageServerSession.ts b/app/ydoc-server/src/languageServerSession.ts index d97d2bf0d90..1bbfb59be9e 100644 --- a/app/ydoc-server/src/languageServerSession.ts +++ b/app/ydoc-server/src/languageServerSession.ts @@ -47,6 +47,7 @@ const EXTENSION = '.enso' const debugLog = createDebug('ydoc-server:session') +/** TODO: Add docs */ export class LanguageServerSession { clientId: Uuid indexDoc: WSSharedDoc @@ -62,6 +63,7 @@ export class LanguageServerSession { static DEBUG = false + /** Create a {@link LanguageServerSession}. */ constructor(url: string) { this.clientScope = new AbortScope() this.clientId = random.uuidv4() as Uuid @@ -89,6 +91,8 @@ export class LanguageServerSession { } static sessions = new Map() + + /** Get a {@link LanguageServerSession} by its URL. */ static get(url: string): LanguageServerSession { const session = map.setIfUndefined( LanguageServerSession.sessions, @@ -201,6 +205,7 @@ export class LanguageServerSession { ) } + /** TODO: Add docs */ async scanSourceFiles() { this.assertProjectRoot() const sourceDir: Path = { rootId: this.projectRootId, segments: [SOURCE_DIR] } @@ -211,11 +216,13 @@ export class LanguageServerSession { ) } + /** TODO: Add docs */ tryGetExistingModuleModel(path: Path): ModulePersistence | undefined { const name = pathToModuleName(path) return this.authoritativeModules.get(name) } + /** TODO: Add docs */ getModuleModel(path: Path): ModulePersistence { const name = pathToModuleName(path) return map.setIfUndefined(this.authoritativeModules, name, () => { @@ -233,10 +240,12 @@ export class LanguageServerSession { }) } + /** TODO: Add docs */ retain() { this.retainCount += 1 } + /** TODO: Add docs */ async release(): Promise { this.retainCount -= 1 if (this.retainCount !== 0) return @@ -249,6 +258,7 @@ export class LanguageServerSession { await Promise.all(moduleDisposePromises) } + /** Get a YDoc by its id. */ getYDoc(guid: string): WSSharedDoc | undefined { return this.docs.get(guid) } @@ -335,9 +345,8 @@ class ModulePersistence extends ObservableV2<{ removed: () => void }> { private setState(state: LsSyncState) { if (this.state !== LsSyncState.Disposed) { debugLog('State change: %o -> %o', LsSyncState[this.state], LsSyncState[state]) - // This is SAFE. `this.state` is only `readonly` to ensure that this is the only place - // where it is mutated. - // @ts-expect-error + // @ts-expect-error This is SAFE. `this.state` is only `readonly` to ensure that + // this is the only place where it is mutated. this.state = state if (state === LsSyncState.Synchronized) this.trySyncRemoveUpdates() } else { @@ -346,9 +355,8 @@ class ModulePersistence extends ObservableV2<{ removed: () => void }> { } private setLastAction(lastAction: Promise) { - // This is SAFE. `this.lastAction` is only `readonly` to ensure that this is the only place - // where it is mutated. - // @ts-expect-error + // @ts-expect-error This is SAFE. `this.lastAction` is only `readonly` to ensure that + // this is the only place where it is mutated. this.lastAction = lastAction.then( () => {}, () => {}, @@ -356,8 +364,10 @@ class ModulePersistence extends ObservableV2<{ removed: () => void }> { return lastAction } - /** Set the current state to the given state while the callback is running. - * Set the current state back to {@link LsSyncState.Synchronized} when the callback finishes. */ + /** + * Set the current state to the given state while the callback is running. + * Set the current state back to {@link LsSyncState.Synchronized} when the callback finishes. + */ private async withState(state: LsSyncState, callback: () => void | Promise): Promise private async withState( state: LsSyncState, diff --git a/app/ydoc-server/src/serialization.ts b/app/ydoc-server/src/serialization.ts index f385dfc6516..0cf94c3626a 100644 --- a/app/ydoc-server/src/serialization.ts +++ b/app/ydoc-server/src/serialization.ts @@ -4,7 +4,8 @@ import * as json from 'lib0/json' import { ExternalId, IdMap, sourceRangeFromKey } from 'ydoc-shared/yjsModel' import * as fileFormat from './fileFormat' -export function deserializeIdMap(idMapJson: string) { +/** Convert a JSON string to an {@link IdMap}. */ +export function deserializeIdMap(idMapJson: string): IdMap { const idMapMeta = fileFormat.tryParseIdMapOrFallback(idMapJson) const idMap = new IdMap() for (const [{ index, size }, id] of idMapMeta) { @@ -18,11 +19,13 @@ export function deserializeIdMap(idMapJson: string) { return idMap } +/** Convert an {@link IdMap} to a JSON string. */ export function serializeIdMap(map: IdMap): string { map.validate() return json.stringify(idMapToArray(map)) } +/** Convert an {@link IdMap} to an array of {@link fileFormat.IdMapEntry}. */ export function idMapToArray(map: IdMap): fileFormat.IdMapEntry[] { const entries: fileFormat.IdMapEntry[] = [] map.entries().forEach(([rangeBuffer, id]) => { @@ -37,7 +40,8 @@ export function idMapToArray(map: IdMap): fileFormat.IdMapEntry[] { return entries } -function idMapCmp(a: fileFormat.IdMapEntry, b: fileFormat.IdMapEntry) { +/** Compare two {@link fileFormat.IdMapEntry}. */ +function idMapCmp(a: fileFormat.IdMapEntry, b: fileFormat.IdMapEntry): number { const val1 = a[0]?.index?.value ?? 0 const val2 = b[0]?.index?.value ?? 0 if (val1 === val2) { diff --git a/app/ydoc-server/src/ydoc.ts b/app/ydoc-server/src/ydoc.ts index 87e9fb14419..f9f62e71bad 100644 --- a/app/ydoc-server/src/ydoc.ts +++ b/app/ydoc-server/src/ydoc.ts @@ -26,9 +26,7 @@ interface AwarenessUpdate { type ConnectionId = YjsConnection | string -/** - * A Yjs document that is shared over multiple websocket connections. - */ +/** A Yjs document that is shared over multiple websocket connections. */ export class WSSharedDoc { doc: Y.Doc /** @@ -38,6 +36,7 @@ export class WSSharedDoc { conns: Map> awareness: Awareness + /** Create a {@link WSSharedDoc}. */ constructor(gc = true) { this.doc = new Y.Doc({ gc }) // this.name = name @@ -68,12 +67,14 @@ export class WSSharedDoc { this.doc.on('update', (update, origin) => this.updateHandler(update, origin)) } + /** Send a message to all connected clients. */ broadcast(message: Uint8Array) { for (const [conn] of this.conns) { if (typeof conn !== 'string') conn.send(message) } } + /** Process an update event from the YDoc document. */ updateHandler(update: Uint8Array, _origin: any) { const encoder = encoding.createEncoder() encoding.writeVarUint(encoder, messageSync) @@ -170,7 +171,7 @@ class YjsConnection extends ObservableV2<{ close(): void }> { } try { this.ws.send(message, error => error && this.close()) - } catch (e) { + } catch { this.close() } } @@ -198,10 +199,10 @@ class YjsConnection extends ObservableV2<{ close(): void }> { break } } - } catch (err) { - console.error(err) - // @ts-ignore - this.wsDoc.doc.emit('error', [err]) + } catch (error) { + console.error(error) + // @ts-expect-error Emit a custom event. + this.wsDoc.doc.emit('error', [error]) } } @@ -214,7 +215,7 @@ class YjsConnection extends ObservableV2<{ close(): void }> { this.ws.close() this.emit('close', []) if (this.wsDoc.conns.size === 0) { - // @ts-ignore + // @ts-expect-error Emit a custom event. this.wsDoc.doc.emit('unload', []) } } diff --git a/app/ydoc-shared/package.json b/app/ydoc-shared/package.json index e96d8b53380..c9631e4eb92 100644 --- a/app/ydoc-shared/package.json +++ b/app/ydoc-shared/package.json @@ -17,6 +17,7 @@ "generate:ast-schema": "cargo run -p enso-parser-schema > src/ast/generated/ast-schema.json", "generate:ast-types": "vite-node ./parser-codegen/index.ts src/ast/generated/ast-schema.json src/ast/generated/ast.ts", "generate:ast-types-lazy": "vite-node ./parser-codegen/index.ts src/ast/generated/ast-schema.json src/ast/generated/ast.ts --if-changed", + "lint": "eslint . --max-warnings=0", "format": "prettier --version && prettier --write src/ && eslint . --fix", "postinstall": "corepack pnpm run generate:ast-schema && corepack pnpm run generate:ast-types-lazy" }, diff --git a/app/ydoc-shared/parser-codegen/codegen.ts b/app/ydoc-shared/parser-codegen/codegen.ts index 77f00ee3ea5..2ec67c50abb 100644 --- a/app/ydoc-shared/parser-codegen/codegen.ts +++ b/app/ydoc-shared/parser-codegen/codegen.ts @@ -35,6 +35,7 @@ const viewIdent = tsf.createIdentifier('view') // === Public API === +/** Generate TypeScript code that implements a given schema. */ export function implement(schema: Schema.Schema): string { const file = ts.createSourceFile('source.ts', '', ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS) const printer = ts.createPrinter({ diff --git a/app/ydoc-shared/parser-codegen/index.ts b/app/ydoc-shared/parser-codegen/index.ts index f9f2daee51f..3411988489a 100644 --- a/app/ydoc-shared/parser-codegen/index.ts +++ b/app/ydoc-shared/parser-codegen/index.ts @@ -27,7 +27,7 @@ function usage(): never { } const schemaContents = await fs.readFile(schemaPath, 'utf8') -var hash = crypto.createHash('sha1') +const hash = crypto.createHash('sha1') hash.update(schemaContents) const schemaHash = hash.digest() @@ -45,7 +45,7 @@ const checkSchemaChanged = async () => { try { const lastDigest = await fs.readFile(`${outputPath}.schema-digest`) return !lastDigest.equals(schemaHash) - } catch (e) { + } catch { return true } } diff --git a/app/ydoc-shared/parser-codegen/serialization.ts b/app/ydoc-shared/parser-codegen/serialization.ts index 78aaff9b617..4ab9684dab7 100644 --- a/app/ydoc-shared/parser-codegen/serialization.ts +++ b/app/ydoc-shared/parser-codegen/serialization.ts @@ -77,6 +77,7 @@ type VisitorApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Exp // === Public API === +/** TODO: Add docs */ export class Type { readonly type: ts.TypeNode readonly reader: ReadApplicator @@ -95,16 +96,19 @@ export class Type { this.size = size } + /** TODO: Add docs */ static Abstract(name: string): Type { const valueReader = callRead(name) return new Type(tsf.createTypeReferenceNode(name), valueReader, 'visitValue', POINTER_SIZE) } + /** TODO: Add docs */ static Concrete(name: string, size: number): Type { const valueReader = callRead(name) return new Type(tsf.createTypeReferenceNode(name), valueReader, 'visitValue', size) } + /** TODO: Add docs */ static Sequence(element: Type): Type { return new Type( tsf.createTypeReferenceNode('IterableIterator', [element.type]), @@ -114,6 +118,7 @@ export class Type { ) } + /** TODO: Add docs */ static Option(element: Type): Type { return new Type( tsf.createUnionTypeNode([element.type, noneType]), @@ -123,6 +128,7 @@ export class Type { ) } + /** TODO: Add docs */ static Result(ok: Type, err: Type): Type { return new Type( support.Result(ok.type, err.type), @@ -179,6 +185,7 @@ export class Type { ) } +/** TODO: Add docs */ export function seekView(view: ts.Expression, address: number): ts.Expression { if (address === 0) { return view @@ -187,10 +194,12 @@ export function seekView(view: ts.Expression, address: number): ts.Expression { } } +/** TODO: Add docs */ export function seekViewDyn(view: ts.Expression, address: ts.Expression): ts.Expression { return tsf.createCallExpression(support.readOffset, [], [view, address]) } +/** TODO: Add docs */ export function abstractTypeVariants(cases: ts.Identifier[]): ts.Statement { const reads = cases.map(c => tsf.createPropertyAccessChain(c, undefined, 'read')) return tsf.createVariableStatement( @@ -209,6 +218,7 @@ export function abstractTypeVariants(cases: ts.Identifier[]): ts.Statement { ) } +/** TODO: Add docs */ export function abstractTypeDeserializer( ident: ts.Identifier, cursorIdent: ts.Identifier, @@ -221,6 +231,7 @@ export function abstractTypeDeserializer( ) } +/** TODO: Add docs */ export function fieldDeserializer( ident: ts.Identifier, type: Type, @@ -239,6 +250,7 @@ export function fieldDeserializer( ) } +/** TODO: Add docs */ export function fieldVisitor( ident: ts.Identifier, type: Type, @@ -273,8 +285,10 @@ function thisAccess(ident: ts.Identifier): ts.PropertyAccessExpression { // === Implementation === -/** Returns a function that, given an expression evaluating to a [`Cursor`], returns an expression applying a - * deserialization method with the given name to the cursor. */ +/** + * Returns a function that, given an expression evaluating to a [`Cursor`], returns an expression applying a + * deserialization method with the given name to the cursor. + */ function primitiveReader(func: ts.Identifier): ReadApplicator { return (view, address) => tsf.createCallExpression(func, [], [view, materializeAddress(address)]) } @@ -322,8 +336,10 @@ function materializeAddress(offset: AccessOffset): ts.Expression { } } -/** Similar to [`readerTransformer`], but for deserialization-transformers that produce a reader by combining two input - * readers. */ +/** + * Similar to [`readerTransformer`], but for deserialization-transformers that produce a reader by combining two input + * readers. + */ function readerTransformerTwoTyped( func: ts.Identifier, ): (readOk: ReadApplicator, readErr: ReadApplicator) => ReadApplicator { @@ -336,6 +352,7 @@ function readerTransformerTwoTyped( } } +/** TODO: Add docs */ export function callRead(ident: string): ReadApplicator { return (view, address) => tsf.createCallExpression( @@ -345,6 +362,7 @@ export function callRead(ident: string): ReadApplicator { ) } +/** TODO: Add docs */ export function createSequenceReader(size: number, reader: ReadApplicator): ReadApplicator { const sizeLiteral = tsf.createNumericLiteral(size) const closure = readerClosure(reader) @@ -390,6 +408,7 @@ function createResultVisitor( ) } +/** TODO: Add docs */ export function visitorClosure( visitor: VisitorApplicator | 'visitValue' | undefined, reader: ReadApplicator, @@ -407,6 +426,7 @@ export function visitorClosure( } } +/** TODO: Add docs */ export function readerClosure(reader: ReadApplicator): ts.Expression { const view = tsf.createIdentifier('view') const address = tsf.createIdentifier('address') diff --git a/app/ydoc-shared/parser-codegen/util.ts b/app/ydoc-shared/parser-codegen/util.ts index f5753295f9d..6b2add01435 100644 --- a/app/ydoc-shared/parser-codegen/util.ts +++ b/app/ydoc-shared/parser-codegen/util.ts @@ -4,11 +4,13 @@ const tsf = ts.factory // === Identifier utilities === +/** Convert an identifier from an arbitrary case into PascalCase. */ export function toPascal(ident: string): string { if (ident.includes('.')) throw new Error('toPascal cannot be applied to a namespaced name.') return changeCase.pascalCase(ident) } +/** Convert an identifier from an arbitrary case into camelCase. */ export function toCamel(ident: string): string { if (ident.includes('.')) throw new Error('toCamel cannot be applied to a namespaced name.') return changeCase.camelCase(ident) @@ -30,10 +32,12 @@ const RENAME = new Map([ ['codeStartUtf16', 'startInCodeBuffer'], ]) +/** Rename certain special-cased identifiers to avoid using language keywords, and for increased clarity. */ export function mapIdent(ident: string): string { return RENAME.get(ident) ?? ident } +/** Return a name with an optional namespace, normalized to PascalCase. */ export function namespacedName(name: string, namespace?: string): string { if (namespace == null) { return toPascal(name) @@ -53,12 +57,17 @@ export const modifiers = { protected: tsf.createModifier(ts.SyntaxKind.ProtectedKeyword), } as const +/** Create a TypeScript assignment statement. */ export function assignmentStatement(left: ts.Expression, right: ts.Expression): ts.Statement { return tsf.createExpressionStatement( tsf.createBinaryExpression(left, ts.SyntaxKind.EqualsToken, right), ) } +/** + * Create a TypeScript `class` constructor that forwards a single parameter to its parent class' + * constructor. + */ export function forwardToSuper( ident: ts.Identifier, type: ts.TypeNode, @@ -75,16 +84,22 @@ export function forwardToSuper( ) } +/** + * Create a TypeScript `switch` statement with an additional `default` case that throws an error + * with the given message. + */ export function casesOrThrow(cases: ts.CaseClause[], error: string): ts.CaseBlock { return tsf.createCaseBlock([...cases, tsf.createDefaultClause([throwError(error)])]) } +/** Create a TypeScript `throw` statement. */ export function throwError(error: string): ts.Statement { return tsf.createThrowStatement( tsf.createNewExpression(tsf.createIdentifier('Error'), [], [tsf.createStringLiteral(error)]), ) } +/** Create a TypeScript `=>` function with the given single expression as its body. */ export function makeArrow(params: ts.BindingName[], expr: ts.Expression) { return tsf.createArrowFunction( [], diff --git a/app/ydoc-shared/src/ast/debug.ts b/app/ydoc-shared/src/ast/debug.ts index 15d0c6388fc..0dfae575f6f 100644 --- a/app/ydoc-shared/src/ast/debug.ts +++ b/app/ydoc-shared/src/ast/debug.ts @@ -1,6 +1,6 @@ import { Ast } from './tree' -/// Returns a GraphViz graph illustrating parent/child relationships in the given subtree. +/** Returns a GraphViz graph illustrating parent/child relationships in the given subtree. */ export function graphParentPointers(ast: Ast) { const sanitize = (id: string) => id.replace('ast:', '').replace(/[^A-Za-z0-9]/g, '') const parentToChild = new Array<{ parent: string; child: string }>() diff --git a/app/ydoc-shared/src/ast/ffi.ts b/app/ydoc-shared/src/ast/ffi.ts index 86a697d8174..b4e07e70c84 100644 --- a/app/ydoc-shared/src/ast/ffi.ts +++ b/app/ydoc-shared/src/ast/ffi.ts @@ -1,6 +1,5 @@ /** * @file Provides the Rust ffi interface. The interface should be kept in sync with polyglot ffi inteface {@link module:ffiPolyglot}. - * * @module ffi */ @@ -15,6 +14,7 @@ import { } from 'rust-ffi' const xxHasher128 = await createXXHash128() +/** Return the xxhash hash for the given buffer. */ export function xxHash128(input: IDataType) { xxHasher128.init() xxHasher128.update(input) diff --git a/app/ydoc-shared/src/ast/index.ts b/app/ydoc-shared/src/ast/index.ts index 956f7823939..dbd9e653729 100644 --- a/app/ydoc-shared/src/ast/index.ts +++ b/app/ydoc-shared/src/ast/index.ts @@ -13,7 +13,8 @@ export * from './token' export * from './tree' declare const brandOwned: unique symbol -/** Used to mark references required to be unique. +/** + * Used to mark references required to be unique. * * Note that the typesystem cannot stop you from copying an `Owned`, * but that is an easy mistake to see (because it occurs locally). @@ -30,6 +31,7 @@ export function asOwned(t: T): Owned { export type NodeChild = { whitespace: string | undefined; node: T } export type RawNodeChild = NodeChild | NodeChild +/** Create a new random {@link ExternalId}. */ export function newExternalId(): ExternalId { return random.uuidv4() as ExternalId } @@ -72,7 +74,8 @@ function unwrapGroups(ast: Ast) { return ast } -/** Tries to recognize inputs that are semantically-equivalent to a sequence of `App`s, and returns the arguments +/** + * Tries to recognize inputs that are semantically-equivalent to a sequence of `App`s, and returns the arguments * identified and LHS of the analyzable chain. * * In particular, this function currently recognizes syntax used in visualization-preprocessor expressions. diff --git a/app/ydoc-shared/src/ast/mutableModule.ts b/app/ydoc-shared/src/ast/mutableModule.ts index 89b278b36a0..a8f3d3bec96 100644 --- a/app/ydoc-shared/src/ast/mutableModule.ts +++ b/app/ydoc-shared/src/ast/mutableModule.ts @@ -49,6 +49,7 @@ type YNodes = Y.Map type UpdateObserver = (update: ModuleUpdate) => void type YjsObserver = (events: Y.YEvent[], transaction: Y.Transaction) => void +/** TODO: Add docs */ export class MutableModule implements Module { private readonly nodes: YNodes private updateObservers: UpdateObserver[] | undefined @@ -66,24 +67,29 @@ export class MutableModule implements Module { return instance as Mutable } + /** TODO: Add docs */ edit(): MutableModule { const doc = new Y.Doc() Y.applyUpdateV2(doc, Y.encodeStateAsUpdateV2(this.ydoc)) return new MutableModule(doc) } + /** TODO: Add docs */ applyEdit(edit: MutableModule, origin: Origin = defaultLocalOrigin) { Y.applyUpdateV2(this.ydoc, Y.encodeStateAsUpdateV2(edit.ydoc), origin) } + /** TODO: Add docs */ transact(f: () => T, origin: Origin = defaultLocalOrigin): T { return this.ydoc.transact(f, origin) } + /** TODO: Add docs */ root(): MutableAst | undefined { return this.rootPointer()?.expression } + /** TODO: Add docs */ replaceRoot(newRoot: Owned | undefined): Owned | undefined { if (newRoot) { const rootPointer = this.rootPointer() @@ -105,11 +111,13 @@ export class MutableModule implements Module { } } + /** TODO: Add docs */ syncRoot(root: Owned) { this.replaceRoot(root) this.gc() } + /** TODO: Add docs */ syncToCode(code: string) { const root = this.root() if (root) { @@ -167,10 +175,12 @@ export class MutableModule implements Module { return materializeMutable(this, fields) as Owned> } + /** TODO: Add docs */ static Transient() { return new this(new Y.Doc()) } + /** TODO: Add docs */ observe(observer: (update: ModuleUpdate) => void) { this.updateObservers ??= [] this.updateObservers.push(observer) @@ -190,6 +200,7 @@ export class MutableModule implements Module { this.nodes.observeDeep(this.yjsObserver) } + /** TODO: Add docs */ unobserve(handle: UpdateObserver) { const i = this.updateObservers?.indexOf(handle) if (i == null || i < 0) return @@ -204,6 +215,7 @@ export class MutableModule implements Module { return !!this.updateObservers?.length } + /** TODO: Add docs */ getStateAsUpdate(): ModuleUpdate { const updateBuilder = new UpdateBuilder(this, this.nodes, undefined) for (const id of this.nodes.keys()) { @@ -213,6 +225,7 @@ export class MutableModule implements Module { return updateBuilder.finish() } + /** TODO: Add docs */ applyUpdate(update: Uint8Array, origin: Origin): ModuleUpdate | undefined { let summary: ModuleUpdate | undefined const observer = (events: Y.YEvent[]) => { @@ -272,12 +285,16 @@ export class MutableModule implements Module { return updateBuilder.finish() } + /** TODO: Add docs */ clear() { this.nodes.clear() } + /** TODO: Add docs */ get(id: AstId): Mutable + /** TODO: Add docs */ get(id: AstId | undefined): Mutable | undefined + /** TODO: Add docs */ get(id: AstId | undefined): Mutable | undefined { if (!id) return undefined const ast = this.tryGet(id) @@ -285,6 +302,7 @@ export class MutableModule implements Module { return ast } + /** TODO: Add docs */ tryGet(id: AstId | undefined): Mutable | undefined { if (!id) return undefined const nodeData = this.nodes.get(id) @@ -293,24 +311,29 @@ export class MutableModule implements Module { return materializeMutable(this, fields) } + /** TODO: Add docs */ replace(id: AstId, value: Owned): Owned | undefined { return this.tryGet(id)?.replace(value) } + /** TODO: Add docs */ replaceValue(id: AstId, value: Owned): Owned | undefined { return this.tryGet(id)?.replaceValue(value) } + /** TODO: Add docs */ take(id: AstId): Owned { return this.replace(id, Wildcard.new(this)) || asOwned(this.get(id)) } + /** TODO: Add docs */ updateValue(id: AstId, f: (x: Owned) => Owned): T | undefined { return this.tryGet(id)?.updateValue(f) } ///////////////////////////////////////////// + /** TODO: Add docs */ constructor(doc: Y.Doc) { this.nodes = doc.getMap('nodes') } @@ -323,9 +346,9 @@ export class MutableModule implements Module { /** @internal */ baseObject(type: string, externalId?: ExternalId, overrideId?: AstId): FixedMap { const map = new Y.Map() - const map_ = map as unknown as FixedMap<{}> + const map_ = map as unknown as FixedMap const id = overrideId ?? newAstId(type) - const metadata = new Y.Map() as unknown as FixedMap<{}> + const metadata = new Y.Map() as unknown as FixedMap const metadataFields = setAll(metadata, { externalId: externalId ?? newExternalId(), }) @@ -342,25 +365,33 @@ export class MutableModule implements Module { /** @internal */ getToken(token: SyncTokenId): Token + /** TODO: Add docs */ getToken(token: SyncTokenId | undefined): Token | undefined + /** TODO: Add docs */ getToken(token: SyncTokenId | undefined): Token | undefined { if (!token) return token if (token instanceof Token) return token return Token.withId(token.code_, token.tokenType_, token.id) } + /** TODO: Add docs */ getAny(node: AstId | SyncTokenId): MutableAst | Token { return isTokenId(node) ? this.getToken(node) : this.get(node) } + /** TODO: Add docs */ getConcrete(child: RawNodeChild): NodeChild | NodeChild { if (isTokenId(child.node)) return { whitespace: child.whitespace, node: this.getToken(child.node) } else return { whitespace: child.whitespace, node: this.get(child.node) } } - /** @internal Copy a node into the module, if it is bound to a different module. */ + /** + * Copy a node into the module, if it is bound to a different module. + * @internal + */ copyIfForeign(ast: Owned): Owned + /** TODO: Add docs */ copyIfForeign(ast: Owned | undefined): Owned | undefined { if (!ast) return ast if (ast.module === this) return ast @@ -390,10 +421,12 @@ export const __TEST = { newAstId } /** Checks whether the input looks like an AstId. */ const astIdRegex = /^ast:[A-Za-z]+#[0-9]+$/ +/** TODO: Add docs */ export function isAstId(value: string): value is AstId { return astIdRegex.test(value) } +/** TODO: Add docs */ export function assertAstId(value: string): asserts value is AstId { assert(isAstId(value), `Incorrect AST ID: ${value}`) } diff --git a/app/ydoc-shared/src/ast/parse.ts b/app/ydoc-shared/src/ast/parse.ts index 0923182498b..2527d598cce 100644 --- a/app/ydoc-shared/src/ast/parse.ts +++ b/app/ydoc-shared/src/ast/parse.ts @@ -104,6 +104,7 @@ export function abstract( code: string, substitutor?: (key: NodeKey) => Owned | undefined, ): { root: Owned; spans: SpanMap; toRaw: Map } +/** Implementation of `abstract`. */ export function abstract( module: MutableModule, tree: RawAst.Tree, @@ -477,7 +478,10 @@ export function print(ast: Ast): PrintedSource { return { info, code } } -/** @internal Used by `Ast.printSubtree`. Note that some AST types have overrides. */ +/** + * Used by `Ast.printSubtree`. Note that some AST types have overrides. + * @internal + */ export function printAst( ast: Ast, info: SpanMap, @@ -519,7 +523,10 @@ export function printAst( return code } -/** @internal Use `Ast.code()' to stringify. */ +/** + * Use `Ast.code()' to stringify. + * @internal + */ export function printBlock( block: BodyBlock, info: SpanMap, @@ -561,7 +568,10 @@ export function printBlock( return code } -/** @internal Use `Ast.code()' to stringify. */ +/** + * Use `Ast.code()' to stringify. + * @internal + */ export function printDocumented( documented: Documented, info: SpanMap, @@ -633,7 +643,8 @@ export function parseModuleWithSpans( return abstract(module ?? MutableModule.Transient(), tree, code) } -/** Parse the input, and apply the given `IdMap`. Return the parsed tree, the updated `IdMap`, the span map, and a +/** + * Parse the input, and apply the given `IdMap`. Return the parsed tree, the updated `IdMap`, the span map, and a * mapping to the `RawAst` representation. */ export function parseExtended(code: string, idMap?: IdMap | undefined, inModule?: MutableModule) { @@ -659,7 +670,8 @@ export function astCount(ast: Ast): number { return count } -/** Apply an `IdMap` to a module, using the given `SpanMap`. +/** + * Apply an `IdMap` to a module, using the given `SpanMap`. * @returns The number of IDs that were assigned from the map. */ export function setExternalIds(edit: MutableModule, spans: SpanMap, ids: IdMap): number { @@ -677,7 +689,8 @@ export function setExternalIds(edit: MutableModule, spans: SpanMap, ids: IdMap): return astsMatched } -/** Try to find all the spans in `expected` in `encountered`. If any are missing, use the provided `code` to determine +/** + * Try to find all the spans in `expected` in `encountered`. If any are missing, use the provided `code` to determine * whether the lost spans are single-line or multi-line. */ function checkSpans(expected: NodeSpanMap, encountered: NodeSpanMap, code: string) { @@ -700,7 +713,8 @@ function checkSpans(expected: NodeSpanMap, encountered: NodeSpanMap, code: strin return { lostInline, lostBlock } } -/** If the input tree's concrete syntax has precedence errors (i.e. its expected code would not parse back to the same +/** + * If the input tree's concrete syntax has precedence errors (i.e. its expected code would not parse back to the same * structure), try to fix it. If possible, it will be repaired by inserting parentheses; if that doesn't fix it, the * affected subtree will be re-synced to faithfully represent the source code the incorrect tree prints to. */ @@ -751,7 +765,6 @@ export function repair( /** * Replace subtrees in the module to ensure that the module contents are consistent with the module's code. - * * @param badAsts - ASTs that, if printed, would not parse to exactly their current content. * @param badSpans - Span map produced by printing the `badAsts` nodes and all their parents. * @param goodSpans - Span map produced by parsing the code from the module of `badAsts`. @@ -793,7 +806,10 @@ function resync( ) } -/** @internal Recursion helper for {@link syntaxHash}. */ +/** + * Recursion helper for {@link syntaxHash}. + * @internal + */ function hashSubtreeSyntax(ast: Ast, hashesOut: Map): SyntaxHash { let content = '' content += ast.typeName + ':' @@ -818,7 +834,8 @@ function hashString(input: string): SyntaxHash { return xxHash128(input) as SyntaxHash } -/** Calculates `SyntaxHash`es for the given node and all its children. +/** + * Calculates `SyntaxHash`es for the given node and all its children. * * Each `SyntaxHash` summarizes the syntactic content of an AST. If two ASTs have the same code and were parsed the * same way (i.e. one was not parsed in a context that resulted in a different interpretation), they will have the same @@ -870,7 +887,7 @@ function calculateCorrespondence( ) const partAfterToAstBefore = new Map() for (const [spanBefore, partAfter] of spansBeforeAndAfter) { - const astBefore = astSpans.get(sourceRangeKey(spanBefore) as NodeKey)?.[0]! + const astBefore = astSpans.get(sourceRangeKey(spanBefore) as NodeKey)![0]! partAfterToAstBefore.set(sourceRangeKey(partAfter), astBefore) } const matchingPartsAfter = spansBeforeAndAfter.map(([_before, after]) => after) diff --git a/app/ydoc-shared/src/ast/parserSupport.ts b/app/ydoc-shared/src/ast/parserSupport.ts index caad26845a5..8a0fc5bce5a 100644 --- a/app/ydoc-shared/src/ast/parserSupport.ts +++ b/app/ydoc-shared/src/ast/parserSupport.ts @@ -16,10 +16,12 @@ export abstract class LazyObject { this._v = view } + /** TODO: Add docs */ visitChildren(_visitor: ObjectVisitor): boolean { return false } + /** TODO: Add docs */ children(): LazyObject[] { const children: LazyObject[] = [] this.visitChildren(child => { @@ -35,40 +37,49 @@ function makeDataView(buffer: ArrayBuffer, address: number) { return new DataView(buffer, address) } +/** TODO: Add docs */ export function readU8(view: DataView, address: number) { return view.getUint8(address) } +/** TODO: Add docs */ export function readU32(view: DataView, address: number) { return view.getUint32(address, true) } +/** TODO: Add docs */ export function readI32(view: DataView, address: number) { return view.getInt32(address, true) } +/** TODO: Add docs */ export function readU64(view: DataView, address: number) { return view.getBigUint64(address, true) } +/** TODO: Add docs */ export function readI64(view: DataView, address: number) { return view.getBigInt64(address, true) } +/** TODO: Add docs */ export function readBool(view: DataView, address: number) { return readU8(view, address) !== 0 } +/** TODO: Add docs */ export function readOffset(view: DataView, offset: number) { return makeDataView(view.buffer, view.byteOffset + offset) } +/** TODO: Add docs */ export function readPointer(view: DataView, address: number): DataView { return makeDataView(view.buffer, readU32(view, address)) } const textDecoder = new TextDecoder() +/** TODO: Add docs */ export function readOption( view: DataView, address: number, @@ -81,6 +92,7 @@ export function readOption( return result } +/** TODO: Add docs */ export function visitOption( view: DataView, address: number, @@ -97,6 +109,7 @@ export function visitOption( } } +/** TODO: Add docs */ export function readResult( view: DataView, address: number, @@ -115,6 +128,7 @@ export function readResult( } } +/** TODO: Add docs */ export function visitResult( view: DataView, address: number, @@ -135,6 +149,7 @@ export function visitResult( } } +/** TODO: Add docs */ export function visitSequence( view: DataView, address: number, @@ -151,6 +166,7 @@ export function visitSequence( return false } +/** TODO: Add docs */ export function readSequence( view: DataView, address: number, @@ -163,12 +179,14 @@ export function readSequence( return new LazySequence(offset, size, end, (offset: number) => reader(data, offset)) } +/** TODO: Add docs */ export class LazySequence implements IterableIterator { private offset: number private readonly step: number private readonly end: number private readonly read: (address: number) => T + /** TODO: Add docs */ constructor(offset: number, step: number, end: number, read: (address: number) => T) { this.read = read this.offset = offset @@ -176,10 +194,12 @@ export class LazySequence implements IterableIterator { this.end = end } + /** TODO: Add docs */ [Symbol.iterator]() { return this } + /** TODO: Add docs */ public next(): IteratorResult { if (this.offset >= this.end) { return { done: true, value: undefined } @@ -190,6 +210,7 @@ export class LazySequence implements IterableIterator { } } +/** TODO: Add docs */ export function readString(view: DataView, address: number): string { const data = readPointer(view, address) const len = readU32(data, 0) @@ -197,6 +218,7 @@ export function readString(view: DataView, address: number): string { return textDecoder.decode(bytes) } +/** TODO: Add docs */ export function readEnum(readers: Reader[], view: DataView, address: number): T { const data = readPointer(view, address) const discriminant = readU32(data, 0) diff --git a/app/ydoc-shared/src/ast/sourceDocument.ts b/app/ydoc-shared/src/ast/sourceDocument.ts index ea1b172b8cf..a8301bedb0d 100644 --- a/app/ydoc-shared/src/ast/sourceDocument.ts +++ b/app/ydoc-shared/src/ast/sourceDocument.ts @@ -5,7 +5,8 @@ import { offsetEdit, textChangeToEdits } from '../util/data/text' import type { Origin, SourceRange } from '../yjsModel' import { rangeEquals, sourceRangeFromKey } from '../yjsModel' -/** Provides a view of the text representation of a module, +/** + * Provides a view of the text representation of a module, * and information about the correspondence between the text and the ASTs, * that can be kept up-to-date by applying AST changes. */ @@ -20,10 +21,12 @@ export class SourceDocument { this.observers = [] } + /** Create an empty {@link SourceDocument}. */ static Empty() { return new this('', new Map()) } + /** Reset this {@link SourceDocument} to an empty state. */ clear() { if (this.spans.size !== 0) this.spans.clear() if (this.text_ !== '') { @@ -33,6 +36,7 @@ export class SourceDocument { } } + /** Apply a {@link ModuleUpdate} and notify observers of the edits. */ applyUpdate(module: Module, update: ModuleUpdate) { for (const id of update.nodesDeleted) this.spans.delete(id) const root = module.root() @@ -65,30 +69,34 @@ export class SourceDocument { } } + /** Get the entire text representation of this module. */ get text(): string { return this.text_ } + /** Get a span in this document by its {@link AstId}. */ getSpan(id: AstId): SourceRange | undefined { return this.spans.get(id) } + /** Add a callback to be called with a list of edits on every update. */ observe(observer: SourceDocumentObserver) { this.observers.push(observer) if (this.text_.length) observer([{ range: [0, 0], insert: this.text_ }], undefined) } + /** Remove a callback to no longer be called with a list of edits on every update. */ unobserve(observer: SourceDocumentObserver) { const index = this.observers.indexOf(observer) if (index !== undefined) this.observers.splice(index, 1) } - private notifyObservers(textEdits: SourceRangeEdit[], origin: Origin | undefined) { + private notifyObservers(textEdits: readonly SourceRangeEdit[], origin: Origin | undefined) { for (const o of this.observers) o(textEdits, origin) } } export type SourceDocumentObserver = ( - textEdits: SourceRangeEdit[], + textEdits: readonly SourceRangeEdit[], origin: Origin | undefined, ) => void diff --git a/app/ydoc-shared/src/ast/text.ts b/app/ydoc-shared/src/ast/text.ts index cfdb87a440a..f09cbdeee4e 100644 --- a/app/ydoc-shared/src/ast/text.ts +++ b/app/ydoc-shared/src/ast/text.ts @@ -52,7 +52,7 @@ function escapeChar(char: string) { /** * Escape a string so it can be safely spliced into an interpolated (`''`) Enso string. * Note: Escape sequences are NOT interpreted in raw (`""`) string literals. - * */ + */ export function escapeTextLiteral(rawString: string) { return rawString.replace(escapeRegex, escapeChar) } diff --git a/app/ydoc-shared/src/ast/token.ts b/app/ydoc-shared/src/ast/token.ts index ecfbd2183a1..e5aa86097ed 100644 --- a/app/ydoc-shared/src/ast/token.ts +++ b/app/ydoc-shared/src/ast/token.ts @@ -6,10 +6,12 @@ import { isUuid } from '../yjsModel' import { is_ident_or_operator } from './ffi' import * as RawAst from './generated/ast' -export function isToken(t: unknown): t is Token { - return t instanceof Token +/** Whether the given value is a {@link Token}. */ +export function isToken(maybeToken: unknown): maybeToken is Token { + return maybeToken instanceof Token } +/** Whether the given {@link NodeChild} is a {@link NodeChild}<{@link Token}>. */ export function isTokenChild(child: NodeChild): child is NodeChild { return isToken(child.node) } @@ -28,6 +30,7 @@ export interface SyncTokenId { readonly tokenType_: RawAst.Token.Type | undefined } +/** A structure representing a lexical source code unit in the AST. */ export class Token implements SyncTokenId { readonly id: TokenId code_: string @@ -39,27 +42,33 @@ export class Token implements SyncTokenId { this.tokenType_ = type } + /** The id of this token. */ get externalId(): TokenId { return this.id } + /** Construct a {@link Token} without a {@link TokenId}. */ static new(code: string, type?: RawAst.Token.Type) { return new this(code, type, newTokenId()) } + /** Construct a {@link Token} with a {@link TokenId}. */ static withId(code: string, type: RawAst.Token.Type | undefined, id: TokenId) { assert(isUuid(id)) return new this(code, type, id) } + /** Whether one {@link SyncTokenId} is equal to another. */ static equal(a: SyncTokenId, b: SyncTokenId): boolean { return a.tokenType_ === b.tokenType_ && a.code_ === b.code_ } + /** The code represented by this token. */ code(): string { return this.code_ } + /** The name of the token type of this token. */ typeName(): string { if (this.tokenType_) return RawAst.Token.typeNames[this.tokenType_]! else return 'Raw' @@ -79,7 +88,8 @@ declare const identifierBrand: unique symbol declare const typeOrConsIdentifierBrand: unique symbol declare const operatorBrand: unique symbol -/** A string representing a valid qualified name of our language. +/** + * A string representing a valid qualified name of our language. * * In our language, the segments are separated by `.`. All the segments except the last must be lexical identifiers. The * last may be an identifier or a lexical operator. A single identifier is also a valid qualified name. @@ -95,7 +105,8 @@ export type TypeOrConstructorIdentifier = Identifier & { [typeOrConsIdentifierBr /** A string representing a lexical operator. */ export type Operator = string & { [operatorBrand]: never; [qualifiedNameBrand]: never } -/** A string that can be parsed as an identifier in some contexts. +/** + * A string that can be parsed as an identifier in some contexts. * * If it is lexically an identifier (see `StrictIdentifier`), it can be used as identifier anywhere. * @@ -105,7 +116,8 @@ export type Operator = string & { [operatorBrand]: never; [qualifiedNameBrand]: */ export type IdentifierOrOperatorIdentifier = Identifier | Operator -/** Returns true if `code` can be used as an identifier in some contexts. +/** + * Returns true if `code` can be used as an identifier in some contexts. * * If it is lexically an identifier (see `isIdentifier`), it can be used as identifier anywhere. * @@ -119,21 +131,26 @@ export function isIdentifierOrOperatorIdentifier( return is_ident_or_operator(code) !== 0 } -/** Returns true if `code` is lexically an identifier. */ +/** Whether the given code is lexically an identifier. */ export function isIdentifier(code: string): code is Identifier { return is_ident_or_operator(code) === 1 } +/** + * Whether the given code is a type or constructor identifier. + * This is true if the code is an identifier beginning with an uppercase letter. + */ export function isTypeOrConsIdentifier(code: string): code is TypeOrConstructorIdentifier { const isUppercase = (s: string) => s.toUpperCase() === s && s.toLowerCase() !== s return isIdentifier(code) && code.length > 0 && isUppercase(code[0]!) } +/** The code as an {@link Identifier} if it is an {@link Identifier}, else `undefined`. */ export function identifier(code: string): Identifier | undefined { if (isIdentifier(code)) return code } -/** Returns true if `code` is lexically an operator. */ +/** Whether the given code is lexically an operator. */ export function isOperator(code: string): code is Operator { return is_ident_or_operator(code) === 2 } diff --git a/app/ydoc-shared/src/ast/tree.ts b/app/ydoc-shared/src/ast/tree.ts index cf3cd527b16..cdeb0be74dc 100644 --- a/app/ydoc-shared/src/ast/tree.ts +++ b/app/ydoc-shared/src/ast/tree.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ import type { Identifier, IdentifierOrOperatorIdentifier, @@ -80,21 +81,25 @@ const astFieldKeys = allKeys({ metadata: null, }) +/** TODO: Add docs */ export abstract class Ast { readonly module: Module /** @internal */ readonly fields: FixedMapView + /** TODO: Add docs */ get id(): AstId { return this.fields.get('id') } + /** TODO: Add docs */ get externalId(): ExternalId { const id = this.fields.get('metadata').get('externalId') assert(id != null) return id } + /** TODO: Add docs */ get nodeMetadata(): NodeMetadata { const metadata = this.fields.get('metadata') return metadata as FixedMapView @@ -105,38 +110,43 @@ export abstract class Ast { return this.fields.get('metadata').toJSON() as any } + /** TODO: Add docs */ typeName(): string { return this.fields.get('type') } - /** - * Return whether `this` and `other` are the same object, possibly in different modules. - */ + /** Return whether `this` and `other` are the same object, possibly in different modules. */ is(other: T): boolean { return this.id === other.id } + /** TODO: Add docs */ innerExpression(): Ast { return this.wrappedExpression()?.innerExpression() ?? this } + /** TODO: Add docs */ wrappedExpression(): Ast | undefined { return undefined } + /** TODO: Add docs */ wrappingExpression(): Ast | undefined { const parent = this.parent() return parent?.wrappedExpression()?.is(this) ? parent : undefined } + /** TODO: Add docs */ wrappingExpressionRoot(): Ast { return this.wrappingExpression()?.wrappingExpressionRoot() ?? this } + /** TODO: Add docs */ documentingAncestor(): Documented | undefined { return this.wrappingExpression()?.documentingAncestor() } + /** TODO: Add docs */ get isBindingStatement(): boolean { const inner = this.wrappedExpression() if (inner) { @@ -146,10 +156,12 @@ export abstract class Ast { } } + /** TODO: Add docs */ code(): string { return print(this).code } + /** TODO: Add docs */ visitRecursive(visit: (node: Ast | Token) => void): void { visit(this) for (const child of this.children()) { @@ -161,6 +173,7 @@ export abstract class Ast { } } + /** TODO: Add docs */ visitRecursiveAst(visit: (ast: Ast) => void | boolean): void { if (visit(this) === false) return for (const child of this.children()) { @@ -168,6 +181,7 @@ export abstract class Ast { } } + /** TODO: Add docs */ printSubtree( info: SpanMap, offset: number, @@ -189,19 +203,23 @@ export abstract class Ast { } } + /** TODO: Add docs */ get parentId(): AstId | undefined { const parentId = this.fields.get('parent') if (parentId !== ROOT_ID) return parentId } + /** TODO: Add docs */ parent(): Ast | undefined { return this.module.get(this.parentId) } + /** TODO: Add docs */ static parseBlock(source: string, module?: MutableModule) { return parseBlock(source, module) } + /** TODO: Add docs */ static parse(source: string, module?: MutableModule) { return parse(source, module) } @@ -213,25 +231,31 @@ export abstract class Ast { this.fields = fields } - /** @internal - * Returns child subtrees, including information about the whitespace between them. + /** + * Returns child subtrees, including information about the whitespace between them. + * @internal */ abstract concreteChildren(verbatim?: boolean): IterableIterator } +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface MutableAst {} +/** TODO: Add docs */ export abstract class MutableAst extends Ast { declare readonly module: MutableModule declare readonly fields: FixedMap + /** TODO: Add docs */ setExternalId(id: ExternalId) { this.fields.get('metadata').set('externalId', id) } + /** TODO: Add docs */ mutableNodeMetadata(): MutableNodeMetadata { const metadata = this.fields.get('metadata') return metadata as FixedMap } + /** TODO: Add docs */ setNodeMetadata(nodeMeta: NodeMetadataFields) { const metadata = this.fields.get('metadata') as unknown as Map for (const [key, value] of Object.entries(nodeMeta)) { @@ -255,7 +279,8 @@ export abstract class MutableAst extends Ast { return asOwned(this) } - /** Change the value of the object referred to by the `target` ID. (The initial ID of `replacement` will be ignored.) + /** + * Change the value of the object referred to by the `target` ID. (The initial ID of `replacement` will be ignored.) * Returns the old value, with a new (unreferenced) ID. */ replaceValue(replacement: Owned): Owned { @@ -266,14 +291,17 @@ export abstract class MutableAst extends Ast { return old } + /** TODO: Add docs */ replaceValueChecked(replacement: Owned): Owned { const parentId = this.fields.get('parent') assertDefined(parentId) return this.replaceValue(replacement) } - /** Replace the parent of this object with a reference to a new placeholder object. - * Returns the object, now parentless, and the placeholder. */ + /** + * Replace the parent of this object with a reference to a new placeholder object. + * Returns the object, now parentless, and the placeholder. + */ takeToReplace(): Removed { if (parentId(this)) { const placeholder = Wildcard.new(this.module) @@ -284,12 +312,15 @@ export abstract class MutableAst extends Ast { } } - /** Replace the parent of this object with a reference to a new placeholder object. - * Returns the object, now parentless. */ + /** + * Replace the parent of this object with a reference to a new placeholder object. + * Returns the object, now parentless. + */ take(): Owned { return this.replace(Wildcard.new(this.module)) } + /** TODO: Add docs */ takeIfParented(): Owned { const parent = parentId(this) if (parent) { @@ -301,16 +332,18 @@ export abstract class MutableAst extends Ast { return asOwned(this) } - /** Replace the value assigned to the given ID with a placeholder. + /** + * Replace the value assigned to the given ID with a placeholder. * Returns the removed value, with a new unreferenced ID. - **/ + */ takeValue(): Removed { const placeholder = Wildcard.new(this.module) const node = this.replaceValue(placeholder) return { node, placeholder } } - /** Take this node from the tree, and replace it with the result of applying the given function to it. + /** + * Take this node from the tree, and replace it with the result of applying the given function to it. * * Note that this is a modification of the *parent* node. Any `Ast` objects or `AstId`s that pointed to the old value * will still point to the old value. @@ -323,7 +356,8 @@ export abstract class MutableAst extends Ast { return replacement } - /** Take this node from the tree, and replace it with the result of applying the given function to it; transfer the + /** + * Take this node from the tree, and replace it with the result of applying the given function to it; transfer the * metadata from this node to the replacement. * * Note that this is a modification of the *parent* node. Any `Ast` objects or `AstId`s that pointed to the old value @@ -337,6 +371,7 @@ export abstract class MutableAst extends Ast { return replacement } + /** TODO: Add docs */ mutableParent(): MutableAst | undefined { const parentId = this.fields.get('parent') if (parentId === 'ROOT_ID') return @@ -353,6 +388,7 @@ export abstract class MutableAst extends Ast { applyTextEditsToAst(this, textEdits, metadataSource ?? this.module) } + /** TODO: Add docs */ getOrInitDocumentation(): MutableDocumented { const existing = this.documentingAncestor() if (existing) return this.module.getVersion(existing) @@ -386,7 +422,9 @@ export abstract class MutableAst extends Ast { /** @internal */ claimChild(child: Owned): AstId + /** TODO: Add docs */ claimChild(child: Owned | undefined): AstId | undefined + /** TODO: Add docs */ claimChild(child: Owned | undefined): AstId | undefined { return child ? claimChild(this.module, child, this.id) : undefined } @@ -438,7 +476,8 @@ function idRewriter( } } -/** Apply the given function to each `AstId` in the fields of `ast`. For each value that it returns an output, that +/** + * Apply the given function to each `AstId` in the fields of `ast`. For each value that it returns an output, that * output will be substituted for the input ID. */ export function rewriteRefs(ast: MutableAst, f: (id: AstId) => AstId | undefined) { @@ -453,7 +492,8 @@ export function rewriteRefs(ast: MutableAst, f: (id: AstId) => AstId | undefined return fieldsChanged } -/** Copy all fields except the `Ast` base fields from `ast2` to `ast1`. A reference-rewriting function will be applied +/** + * Copy all fields except the `Ast` base fields from `ast2` to `ast1`. A reference-rewriting function will be applied * to `AstId`s in copied fields; see {@link rewriteRefs}. */ export function syncFields(ast1: MutableAst, ast2: Ast, f: (id: AstId) => AstId | undefined) { @@ -463,6 +503,7 @@ export function syncFields(ast1: MutableAst, ast2: Ast, f: (id: AstId) => AstId } } +/** TODO: Add docs */ export function syncNodeMetadata(target: MutableNodeMetadata, source: NodeMetadata) { const oldPos = target.get('position') const newPos = source.get('position') @@ -590,17 +631,21 @@ interface NameSpecification { name: T['token'] equals: T['token'] } +/** TODO: Add docs */ export class App extends Ast { declare fields: FixedMap + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableApp) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, func: NodeChild, @@ -619,6 +664,7 @@ export class App extends Ast { return asOwned(new MutableApp(module, fields)) } + /** TODO: Add docs */ static new( module: MutableModule, func: Owned, @@ -634,10 +680,12 @@ export class App extends Ast { ) } + /** TODO: Add docs */ static positional(func: Owned, argument: Owned, module?: MutableModule): Owned { return App.new(module ?? MutableModule.Transient(), func, undefined, argument) } + /** TODO: Add docs */ static PositionalSequence(func: Owned, args: Owned[]): Owned { return args.reduce( (expression, argument) => App.new(func.module, expression, undefined, argument), @@ -645,16 +693,20 @@ export class App extends Ast { ) } + /** TODO: Add docs */ get function(): Ast { return this.module.get(this.fields.get('function').node) } + /** TODO: Add docs */ get argumentName(): Token | undefined { return this.module.getToken(this.fields.get('nameSpecification')?.name.node) } + /** TODO: Add docs */ get argument(): Ast { return this.module.get(this.fields.get('argument').node) } + /** TODO: Add docs */ *concreteChildren(verbatim?: boolean): IterableIterator { const { function: function_, parens, nameSpecification, argument } = getAll(this.fields) yield ensureUnspaced(function_, verbatim) @@ -708,6 +760,7 @@ function preferUnspaced(child: NodeChild): ConcreteChild { function preferSpaced(child: NodeChild): ConcreteChild { return tryAsConcrete(child) ?? { ...child, whitespace: ' ' } } +/** TODO: Add docs */ export class MutableApp extends App implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -732,17 +785,21 @@ interface UnaryOprAppFields { operator: NodeChild argument: NodeChild | undefined } +/** TODO: Add docs */ export class UnaryOprApp extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableUnaryOprApp) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, operator: NodeChild, @@ -757,23 +814,28 @@ export class UnaryOprApp extends Ast { return asOwned(new MutableUnaryOprApp(module, fields)) } + /** TODO: Add docs */ static new(module: MutableModule, operator: Token, argument: Owned | undefined) { return this.concrete(module, unspaced(operator), argument ? autospaced(argument) : undefined) } + /** TODO: Add docs */ get operator(): Token { return this.module.getToken(this.fields.get('operator').node) } + /** TODO: Add docs */ get argument(): Ast | undefined { return this.module.get(this.fields.get('argument')?.node) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { operator, argument } = getAll(this.fields) yield operator if (argument) yield argument } } +/** TODO: Add docs */ export class MutableUnaryOprApp extends UnaryOprApp implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -794,16 +856,20 @@ interface AutoscopedIdentifierFields { operator: NodeChild identifier: NodeChild } +/** TODO: Add docs */ export class AutoscopedIdentifier extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ get identifier(): Token { return this.module.getToken(this.fields.get('identifier').node) } + /** TODO: Add docs */ static tryParse( source: string, module?: MutableModule, @@ -812,6 +878,7 @@ export class AutoscopedIdentifier extends Ast { if (parsed instanceof MutableAutoscopedIdentifier) return parsed } + /** TODO: Add docs */ static concrete(module: MutableModule, operator: NodeChild, identifier: NodeChild) { const base = module.baseObject('AutoscopedIdentifier') const fields = composeFieldData(base, { @@ -821,6 +888,7 @@ export class AutoscopedIdentifier extends Ast { return asOwned(new MutableAutoscopedIdentifier(module, fields)) } + /** TODO: Add docs */ static new( identifier: TypeOrConstructorIdentifier, module?: MutableModule, @@ -831,12 +899,14 @@ export class AutoscopedIdentifier extends Ast { return this.concrete(module_, unspaced(operator), unspaced(ident)) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { operator, identifier } = getAll(this.fields) yield operator yield identifier } } +/** TODO: Add docs */ export class MutableAutoscopedIdentifier extends AutoscopedIdentifier implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -853,17 +923,21 @@ interface NegationAppFields { operator: NodeChild argument: NodeChild } +/** TODO: Add docs */ export class NegationApp extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableNegationApp) return parsed } + /** TODO: Add docs */ static concrete(module: MutableModule, operator: NodeChild, argument: NodeChild) { const base = module.baseObject('NegationApp') const id_ = base.get('id') @@ -874,24 +948,29 @@ export class NegationApp extends Ast { return asOwned(new MutableNegationApp(module, fields)) } + /** TODO: Add docs */ static new(module: MutableModule, argument: Owned) { const minus = Token.new('-', RawAst.Token.Type.Operator) return this.concrete(module, unspaced(minus), unspaced(argument)) } + /** TODO: Add docs */ get operator(): Token { return this.module.getToken(this.fields.get('operator').node) } + /** TODO: Add docs */ get argument(): Ast { return this.module.get(this.fields.get('argument').node) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { operator, argument } = getAll(this.fields) yield operator if (argument) yield argument } } +/** TODO: Add docs */ export class MutableNegationApp extends NegationApp implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -910,17 +989,21 @@ interface OprAppFields { operators: NodeChild[] rhs: NodeChild | undefined } +/** TODO: Add docs */ export class OprApp extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableOprApp) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, lhs: NodeChild | undefined, @@ -937,6 +1020,7 @@ export class OprApp extends Ast { return asOwned(new MutableOprApp(module, fields)) } + /** TODO: Add docs */ static new( module: MutableModule, lhs: Owned | undefined, @@ -948,9 +1032,11 @@ export class OprApp extends Ast { return OprApp.concrete(module, unspaced(lhs), [autospaced(operatorToken)], autospaced(rhs)) } + /** TODO: Add docs */ get lhs(): Ast | undefined { return this.module.get(this.fields.get('lhs')?.node) } + /** TODO: Add docs */ get operator(): Result[]> { const operators = this.fields.get('operators') const operators_ = operators.map(child => ({ @@ -960,10 +1046,12 @@ export class OprApp extends Ast { const [opr] = operators_ return opr ? Ok(opr.node) : Err(operators_) } + /** TODO: Add docs */ get rhs(): Ast | undefined { return this.module.get(this.fields.get('rhs')?.node) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { lhs, operators, rhs } = getAll(this.fields) if (lhs) yield lhs @@ -971,6 +1059,7 @@ export class OprApp extends Ast { if (rhs) yield rhs } } +/** TODO: Add docs */ export class MutableOprApp extends OprApp implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -996,12 +1085,15 @@ interface PropertyAccessFields { operator: NodeChild rhs: NodeChild } +/** TODO: Add docs */ export class PropertyAccess extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse( source: string, module?: MutableModule, @@ -1010,6 +1102,7 @@ export class PropertyAccess extends Ast { if (parsed instanceof MutablePropertyAccess) return parsed } + /** TODO: Add docs */ static new(module: MutableModule, lhs: Owned, rhs: IdentLike, style?: { spaced?: boolean }) { const dot = Token.new('.', RawAst.Token.Type.Operator) const whitespace = style?.spaced ? ' ' : '' @@ -1021,18 +1114,22 @@ export class PropertyAccess extends Ast { ) } + /** TODO: Add docs */ static Sequence( segments: [StrictIdentLike, ...StrictIdentLike[]], module: MutableModule, ): Owned | Owned + /** TODO: Add docs */ static Sequence( segments: [StrictIdentLike, ...StrictIdentLike[], IdentLike], module: MutableModule, ): Owned | Owned + /** TODO: Add docs */ static Sequence( segments: IdentLike[], module: MutableModule, ): Owned | Owned | undefined + /** TODO: Add docs */ static Sequence( segments: IdentLike[], module: MutableModule, @@ -1047,6 +1144,7 @@ export class PropertyAccess extends Ast { if (!operatorInNonFinalSegment) return path } + /** TODO: Add docs */ static concrete( module: MutableModule, lhs: NodeChild | undefined, @@ -1063,18 +1161,22 @@ export class PropertyAccess extends Ast { return asOwned(new MutablePropertyAccess(module, fields)) } + /** TODO: Add docs */ get lhs(): Ast | undefined { return this.module.get(this.fields.get('lhs')?.node) } + /** TODO: Add docs */ get operator(): Token { return this.module.getToken(this.fields.get('operator').node) } + /** TODO: Add docs */ get rhs(): IdentifierOrOperatorIdentifierToken { const ast = this.module.get(this.fields.get('rhs').node) assert(ast instanceof Ident) return ast.token as IdentifierOrOperatorIdentifierToken } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { lhs, operator, rhs } = getAll(this.fields) if (lhs) yield lhs @@ -1082,6 +1184,7 @@ export class PropertyAccess extends Ast { yield rhs } } +/** TODO: Add docs */ export class MutablePropertyAccess extends PropertyAccess implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1100,8 +1203,10 @@ export interface MutablePropertyAccess extends PropertyAccess, MutableAst { } applyMixins(MutablePropertyAccess, [MutableAst]) -/** Unroll the provided chain of `PropertyAccess` nodes, returning the first non-access as `subject` and the accesses - * from left-to-right. */ +/** + * Unroll the provided chain of `PropertyAccess` nodes, returning the first non-access as `subject` and the accesses + * from left-to-right. + */ export function accessChain(ast: Ast): { subject: Ast; accessChain: PropertyAccess[] } { const accessChain = new Array() while (ast instanceof PropertyAccess && ast.lhs) { @@ -1115,12 +1220,15 @@ export function accessChain(ast: Ast): { subject: Ast; accessChain: PropertyAcce interface GenericFields { children: RawNodeChild[] } +/** TODO: Add docs */ export class Generic extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static concrete(module: MutableModule, children: (NodeChild | NodeChild)[]) { const base = module.baseObject('Generic') const id_ = base.get('id') @@ -1130,10 +1238,12 @@ export class Generic extends Ast { return asOwned(new MutableGeneric(module, fields)) } + /** TODO: Add docs */ concreteChildren(_verbatim?: boolean): IterableIterator { return this.fields.get('children')[Symbol.iterator]() } } +/** TODO: Add docs */ export class MutableGeneric extends Generic implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1183,36 +1293,46 @@ interface ImportFields extends FieldObject { hiding: MultiSegmentAppSegment | undefined } +/** TODO: Add docs */ export class Import extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableImport) return parsed } + /** TODO: Add docs */ get polyglot(): Ast | undefined { return this.module.get(this.fields.get('polyglot')?.body?.node) } + /** TODO: Add docs */ get from(): Ast | undefined { return this.module.get(this.fields.get('from')?.body?.node) } + /** TODO: Add docs */ get import_(): Ast | undefined { return this.module.get(this.fields.get('import').body?.node) } + /** TODO: Add docs */ get all(): Token | undefined { return this.module.getToken(this.fields.get('all')?.node) } + /** TODO: Add docs */ get as(): Ast | undefined { return this.module.get(this.fields.get('as')?.body?.node) } + /** TODO: Add docs */ get hiding(): Ast | undefined { return this.module.get(this.fields.get('hiding')?.body?.node) } + /** TODO: Add docs */ static concrete( module: MutableModule, polyglot: MultiSegmentAppSegment | undefined, @@ -1237,6 +1357,7 @@ export class Import extends Ast { return asOwned(new MutableImport(module, fields)) } + /** TODO: Add docs */ static Qualified(path: IdentLike[], module: MutableModule): Owned | undefined { const path_ = PropertyAccess.Sequence(path, module) if (!path_) return @@ -1251,6 +1372,7 @@ export class Import extends Ast { ) } + /** TODO: Add docs */ static Unqualified( path: IdentLike[], name: IdentLike, @@ -1270,6 +1392,7 @@ export class Import extends Ast { ) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const segment = (segment: MultiSegmentAppSegment | undefined) => { const parts = [] @@ -1286,6 +1409,7 @@ export class Import extends Ast { yield* segment(hiding) } } +/** TODO: Add docs */ export class MutableImport extends Import implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1435,17 +1559,21 @@ interface TextLiteralFields { elements: TextElement[] close: NodeChild | undefined } +/** TODO: Add docs */ export class TextLiteral extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableTextLiteral) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, open: NodeChild | undefined, @@ -1464,6 +1592,7 @@ export class TextLiteral extends Ast { return asOwned(new MutableTextLiteral(module, fields)) } + /** TODO: Add docs */ static new(rawText: string, module?: MutableModule): Owned { const escaped = escapeTextLiteral(rawText) const parsed = parse(`'${escaped}'`, module) @@ -1483,6 +1612,7 @@ export class TextLiteral extends Ast { return uninterpolatedText(this.fields.get('elements'), this.module) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { open, newline, elements, close } = getAll(this.fields) if (open) yield open @@ -1491,27 +1621,33 @@ export class TextLiteral extends Ast { if (close) yield close } + /** TODO: Add docs */ boundaryTokenCode(): string | undefined { return (this.open || this.close)?.code() } + /** TODO: Add docs */ isInterpolated(): boolean { const token = this.boundaryTokenCode() return token === "'" || token === "'''" } + /** TODO: Add docs */ get open(): Token | undefined { return this.module.getToken(this.fields.get('open')?.node) } + /** TODO: Add docs */ get close(): Token | undefined { return this.module.getToken(this.fields.get('close')?.node) } + /** TODO: Add docs */ get elements(): TextElement[] { return this.fields.get('elements').map(e => mapRefs(e, rawToConcrete(this.module))) } } +/** TODO: Add docs */ export class MutableTextLiteral extends TextLiteral implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1557,17 +1693,21 @@ interface DocumentedFields { newlines: NodeChild[] expression: NodeChild | undefined } +/** TODO: Add docs */ export class Documented extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableDocumented) return parsed } + /** TODO: Add docs */ static new(text: string, expression: Owned) { return this.concrete( expression.module, @@ -1578,6 +1718,7 @@ export class Documented extends Ast { ) } + /** TODO: Add docs */ static concrete( module: MutableModule, open: NodeChild | undefined, @@ -1596,6 +1737,7 @@ export class Documented extends Ast { return asOwned(new MutableDocumented(module, fields)) } + /** TODO: Add docs */ get expression(): Ast | undefined { return this.module.get(this.fields.get('expression')?.node) } @@ -1606,14 +1748,17 @@ export class Documented extends Ast { return raw.startsWith(' ') ? raw.slice(1) : raw } + /** TODO: Add docs */ override wrappedExpression(): Ast | undefined { return this.expression } + /** TODO: Add docs */ override documentingAncestor(): Documented | undefined { return this } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { open, elements, newlines, expression } = getAll(this.fields) yield open @@ -1622,6 +1767,7 @@ export class Documented extends Ast { if (expression) yield expression } + /** TODO: Add docs */ override printSubtree( info: SpanMap, offset: number, @@ -1631,6 +1777,7 @@ export class Documented extends Ast { return printDocumented(this, info, offset, parentIndent, verbatim) } } +/** TODO: Add docs */ export class MutableDocumented extends Documented implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1672,25 +1819,31 @@ function textToUninterpolatedElements(text: string): TextToken[] { interface InvalidFields { expression: NodeChild } +/** TODO: Add docs */ export class Invalid extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static concrete(module: MutableModule, expression: NodeChild) { const base = module.baseObject('Invalid') return asOwned(new MutableInvalid(module, invalidFields(module, base, expression))) } + /** TODO: Add docs */ get expression(): Ast { return this.module.get(this.fields.get('expression').node) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { yield this.fields.get('expression') } + /** TODO: Add docs */ override printSubtree( info: SpanMap, offset: number, @@ -1700,6 +1853,7 @@ export class Invalid extends Ast { return super.printSubtree(info, offset, parentIndent, true) } } +/** TODO: Add docs */ export function invalidFields( module: MutableModule, base: FixedMap, @@ -1708,13 +1862,16 @@ export function invalidFields( const id_ = base.get('id') return composeFieldData(base, { expression: concreteChild(module, expression, id_) }) } +/** TODO: Add docs */ export class MutableInvalid extends Invalid implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap } export interface MutableInvalid extends Invalid, MutableAst { - /** The `expression` getter is intentionally not narrowed to provide mutable access: - * It makes more sense to `.replace` the `Invalid` node. */ + /** + * The `expression` getter is intentionally not narrowed to provide mutable access: + * It makes more sense to `.replace` the `Invalid` node. + */ } applyMixins(MutableInvalid, [MutableAst]) @@ -1723,17 +1880,21 @@ interface GroupFields { expression: NodeChild | undefined close: NodeChild | undefined } +/** TODO: Add docs */ export class Group extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableGroup) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, open: NodeChild | undefined, @@ -1750,20 +1911,24 @@ export class Group extends Ast { return asOwned(new MutableGroup(module, fields)) } + /** TODO: Add docs */ static new(module: MutableModule, expression: Owned) { const open = unspaced(Token.new('(', RawAst.Token.Type.OpenSymbol)) const close = unspaced(Token.new(')', RawAst.Token.Type.CloseSymbol)) return this.concrete(module, open, unspaced(expression), close) } + /** TODO: Add docs */ get expression(): Ast | undefined { return this.module.get(this.fields.get('expression')?.node) } + /** TODO: Add docs */ override wrappedExpression(): Ast | undefined { return this.expression } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { open, expression, close } = getAll(this.fields) if (open) yield open @@ -1771,6 +1936,7 @@ export class Group extends Ast { if (close) yield close } } +/** TODO: Add docs */ export class MutableGroup extends Group implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1787,12 +1953,15 @@ applyMixins(MutableGroup, [MutableAst]) interface NumericLiteralFields { tokens: NodeChild[] } +/** TODO: Add docs */ export class NumericLiteral extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse( source: string, module?: MutableModule, @@ -1801,6 +1970,7 @@ export class NumericLiteral extends Ast { if (parsed instanceof MutableNumericLiteral) return parsed } + /** TODO: Add docs */ static tryParseWithSign( source: string, module?: MutableModule, @@ -1813,16 +1983,19 @@ export class NumericLiteral extends Ast { return parsed } + /** TODO: Add docs */ static concrete(module: MutableModule, tokens: NodeChild[]) { const base = module.baseObject('NumericLiteral') const fields = composeFieldData(base, { tokens }) return asOwned(new MutableNumericLiteral(module, fields)) } + /** TODO: Add docs */ concreteChildren(_verbatim?: boolean): IterableIterator { return this.fields.get('tokens')[Symbol.iterator]() } } +/** TODO: Add docs */ export class MutableNumericLiteral extends NumericLiteral implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -1830,6 +2003,7 @@ export class MutableNumericLiteral extends NumericLiteral implements MutableAst export interface MutableNumericLiteral extends NumericLiteral, MutableAst {} applyMixins(MutableNumericLiteral, [MutableAst]) +/** TODO: Add docs */ export function isNumericLiteral(code: string) { return is_numeric_literal(code) } @@ -1861,29 +2035,36 @@ export interface FunctionFields { equals: NodeChild body: NodeChild | undefined } +/** TODO: Add docs */ export class Function extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableFunction) return parsed } + /** TODO: Add docs */ get name(): Ast { return this.module.get(this.fields.get('name').node) } + /** TODO: Add docs */ get body(): Ast | undefined { return this.module.get(this.fields.get('body')?.node) } + /** TODO: Add docs */ get argumentDefinitions(): ArgumentDefinition[] { return this.fields .get('argumentDefinitions') .map(def => mapRefs(def, rawToConcrete(this.module))) } + /** TODO: Add docs */ static concrete( module: MutableModule, name: NodeChild, @@ -1902,6 +2083,7 @@ export class Function extends Ast { return asOwned(new MutableFunction(module, fields)) } + /** TODO: Add docs */ static new( module: MutableModule, name: IdentLike, @@ -1937,6 +2119,7 @@ export class Function extends Ast { return MutableFunction.new(module, name, argumentDefinitions, body) } + /** TODO: Add docs */ *bodyExpressions(): IterableIterator { const body = this.body if (body instanceof BodyBlock) { @@ -1946,10 +2129,12 @@ export class Function extends Ast { } } + /** TODO: Add docs */ override get isBindingStatement(): boolean { return true } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { const { name, argumentDefinitions, equals, body } = getAll(this.fields) yield name @@ -1974,6 +2159,7 @@ export class Function extends Ast { if (body) yield preferSpacedIf(body, this.module.tryGet(body.node) instanceof BodyBlock) } } +/** TODO: Add docs */ export class MutableFunction extends Function implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -2012,17 +2198,21 @@ interface AssignmentFields { equals: NodeChild expression: NodeChild } +/** TODO: Add docs */ export class Assignment extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableAssignment) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, pattern: NodeChild, @@ -2039,6 +2229,7 @@ export class Assignment extends Ast { return asOwned(new MutableAssignment(module, fields)) } + /** TODO: Add docs */ static new(module: MutableModule, ident: StrictIdentLike, expression: Owned) { return Assignment.concrete( module, @@ -2048,17 +2239,21 @@ export class Assignment extends Ast { ) } + /** TODO: Add docs */ get pattern(): Ast { return this.module.get(this.fields.get('pattern').node) } + /** TODO: Add docs */ get expression(): Ast { return this.module.get(this.fields.get('expression').node) } + /** TODO: Add docs */ override get isBindingStatement(): boolean { return true } + /** TODO: Add docs */ *concreteChildren(verbatim?: boolean): IterableIterator { const { pattern, equals, expression } = getAll(this.fields) yield ensureUnspaced(pattern, verbatim) @@ -2066,6 +2261,7 @@ export class Assignment extends Ast { yield preferSpaced(expression) } } +/** TODO: Add docs */ export class MutableAssignment extends Assignment implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -2086,17 +2282,21 @@ applyMixins(MutableAssignment, [MutableAst]) interface BodyBlockFields { lines: RawBlockLine[] } +/** TODO: Add docs */ export class BodyBlock extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableBodyBlock) return parsed } + /** TODO: Add docs */ static concrete(module: MutableModule, lines: OwnedBlockLine[]) { const base = module.baseObject('BodyBlock') const id_ = base.get('id') @@ -2106,20 +2306,24 @@ export class BodyBlock extends Ast { return asOwned(new MutableBodyBlock(module, fields)) } + /** TODO: Add docs */ static new(lines: OwnedBlockLine[], module: MutableModule) { return BodyBlock.concrete(module, lines) } + /** TODO: Add docs */ get lines(): BlockLine[] { return this.fields.get('lines').map(line => lineFromRaw(line, this.module)) } + /** TODO: Add docs */ *statements(): IterableIterator { for (const line of this.lines) { if (line.expression) yield line.expression.node } } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { for (const line of this.fields.get('lines')) { yield preferUnspaced(line.newline) @@ -2127,6 +2331,7 @@ export class BodyBlock extends Ast { } } + /** TODO: Add docs */ override printSubtree( info: SpanMap, offset: number, @@ -2136,6 +2341,7 @@ export class BodyBlock extends Ast { return printBlock(this, info, offset, parentIndent, verbatim) } } +/** TODO: Add docs */ export class MutableBodyBlock extends BodyBlock implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -2245,31 +2451,38 @@ function lineToRaw(line: OwnedBlockLine, module: MutableModule, block: AstId): R interface IdentFields { token: NodeChild } +/** TODO: Add docs */ export class Ident extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableIdent) return parsed } + /** TODO: Add docs */ get token(): IdentifierToken { return this.module.getToken(this.fields.get('token').node) as IdentifierToken } + /** TODO: Add docs */ isTypeOrConstructor(): boolean { return /^[A-Z]/.test(this.token.code()) } + /** TODO: Add docs */ static concrete(module: MutableModule, token: NodeChild) { const base = module.baseObject('Ident') const fields = composeFieldData(base, { token }) return asOwned(new MutableIdent(module, fields)) } + /** TODO: Add docs */ static new(module: MutableModule, ident: StrictIdentLike) { return Ident.concrete(module, unspaced(toIdentStrict(ident))) } @@ -2279,14 +2492,17 @@ export class Ident extends Ast { return Ident.concrete(module, unspaced(toIdent(ident))) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { yield this.fields.get('token') } + /** TODO: Add docs */ override code(): Identifier { return this.token.code() as Identifier } } +/** TODO: Add docs */ export class MutableIdent extends Ident implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -2305,37 +2521,45 @@ applyMixins(MutableIdent, [MutableAst]) interface WildcardFields { token: NodeChild } +/** TODO: Add docs */ export class Wildcard extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableWildcard) return parsed } + /** TODO: Add docs */ get token(): Token { return this.module.getToken(this.fields.get('token').node) } + /** TODO: Add docs */ static concrete(module: MutableModule, token: NodeChild) { const base = module.baseObject('Wildcard') const fields = composeFieldData(base, { token }) return asOwned(new MutableWildcard(module, fields)) } + /** TODO: Add docs */ static new(module?: MutableModule) { const token = Token.new('_', RawAst.Token.Type.Wildcard) return this.concrete(module ?? MutableModule.Transient(), unspaced(token)) } + /** TODO: Add docs */ *concreteChildren(_verbatim?: boolean): IterableIterator { yield this.fields.get('token') } } +/** TODO: Add docs */ export class MutableWildcard extends Wildcard implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -2359,17 +2583,21 @@ interface VectorFields { elements: VectorElement[] close: NodeChild } +/** TODO: Add docs */ export class Vector extends Ast { declare fields: FixedMapView + /** TODO: Add docs */ constructor(module: Module, fields: FixedMapView) { super(module, fields) } + /** TODO: Add docs */ static tryParse(source: string, module?: MutableModule): Owned | undefined { const parsed = parse(source, module) if (parsed instanceof MutableVector) return parsed } + /** TODO: Add docs */ static concrete( module: MutableModule, open: NodeChild | undefined, @@ -2386,6 +2614,7 @@ export class Vector extends Ast { return asOwned(new MutableVector(module, fields)) } + /** TODO: Add docs */ static new(module: MutableModule, elements: Owned[]) { return this.concrete( module, @@ -2395,16 +2624,19 @@ export class Vector extends Ast { ) } + /** TODO: Add docs */ static tryBuild( inputs: Iterable, elementBuilder: (input: T, module: MutableModule) => Owned, edit?: MutableModule, ): Owned + /** TODO: Add docs */ static tryBuild( inputs: Iterable, elementBuilder: (input: T, module: MutableModule) => Owned | undefined, edit?: MutableModule, ): Owned | undefined + /** TODO: Add docs */ static tryBuild( inputs: Iterable, valueBuilder: (input: T, module: MutableModule) => Owned | undefined, @@ -2420,6 +2652,7 @@ export class Vector extends Ast { return Vector.concrete(module, undefined, elements, undefined) } + /** TODO: Add docs */ static build( inputs: Iterable, elementBuilder: (input: T, module: MutableModule) => Owned, @@ -2428,6 +2661,7 @@ export class Vector extends Ast { return Vector.tryBuild(inputs, elementBuilder, edit) } + /** TODO: Add docs */ *concreteChildren(verbatim?: boolean): IterableIterator { const { open, elements, close } = getAll(this.fields) yield ensureUnspaced(open, verbatim) @@ -2444,21 +2678,25 @@ export class Vector extends Ast { yield preferUnspaced(close) } + /** TODO: Add docs */ *values(): IterableIterator { for (const element of this.fields.get('elements')) if (element.value) yield this.module.get(element.value.node) } + /** TODO: Add docs */ *enumerate(): IterableIterator<[number, Ast | undefined]> { for (const [index, element] of this.fields.get('elements').entries()) { yield [index, this.module.get(element.value?.node)] } } + /** TODO: Add docs */ get length() { return this.fields.get('elements').length } } +/** TODO: Add docs */ export class MutableVector extends Vector implements MutableAst { declare readonly module: MutableModule declare readonly fields: FixedMap @@ -2571,6 +2809,7 @@ export type Mutable = : T extends Wildcard ? MutableWildcard : MutableAst +/** TODO: Add docs */ export function materializeMutable(module: MutableModule, fields: FixedMap): MutableAst { const type = fields.get('type') const fieldsForType = fields as FixedMap @@ -2617,6 +2856,7 @@ export function materializeMutable(module: MutableModule, fields: FixedMap): Ast { const type = fields.get('type') const fields_ = fields as FixedMapView @@ -2665,7 +2905,10 @@ export function materialize(module: Module, fields: FixedMapView): As export interface FixedMapView { get(key: Key): DeepReadonly - /** @internal Unsafe. The caller must ensure the yielded values are not modified. */ + /** + * Unsafe. The caller must ensure the yielded values are not modified. + * @internal + */ entries(): IterableIterator clone(): FixedMap has(key: string): boolean @@ -2685,7 +2928,8 @@ function getAll(map: FixedMapView): DeepReadonlyF } declare const brandLegalFieldContent: unique symbol -/** Used to add a constraint to all `AstFields`s subtypes ensuring that they were produced by `composeFieldData`, which +/** + * Used to add a constraint to all `AstFields`s subtypes ensuring that they were produced by `composeFieldData`, which * enforces a requirement that the provided fields extend `FieldObject`. */ interface LegalFieldContent { @@ -2705,8 +2949,10 @@ export function setAll>( return map_ } -/** Modifies the input `map`. Returns the same object with an extended type. The added fields are required to have only - * types extending `FieldData`; the returned object is branded as `LegalFieldContent`. */ +/** + * Modifies the input `map`. Returns the same object with an extended type. The added fields are required to have only + * types extending `FieldData`; the returned object is branded as `LegalFieldContent`. + */ export function composeFieldData>( map: FixedMap, fields: Fields2, @@ -2828,6 +3074,7 @@ function unspaced(node: T | undefined): NodeChild export function autospaced(node: T): NodeChild export function autospaced(node: T | undefined): NodeChild | undefined +/** TODO: Add docs */ export function autospaced( node: T | undefined, ): NodeChild | undefined { diff --git a/app/ydoc-shared/src/binaryProtocol.ts b/app/ydoc-shared/src/binaryProtocol.ts index c7ca3649d34..3d303b9688e 100644 --- a/app/ydoc-shared/src/binaryProtocol.ts +++ b/app/ydoc-shared/src/binaryProtocol.ts @@ -236,6 +236,7 @@ export type Table = { bbPos: number } +/** A helper to incrementally build a message buffer. */ export class Builder { private bb: ByteBuffer private space: number @@ -249,12 +250,14 @@ export class Builder { private _forceDefaults = false private stringMaps: Map | null = null + /** Create a {@link Builder}. */ constructor(initialSize?: number) { initialSize ??= 1024 this.bb = new ByteBuffer(new ArrayBuffer(initialSize)) this.space = initialSize } + /** Reset the state of the internal buffer. */ clear(): void { this.bb.position = 0 this.space = this.bb.view.byteLength @@ -273,17 +276,18 @@ export class Builder { * In order to save space, fields that are set to their default value * don't get serialized into the buffer. Forcing defaults provides a * way to manually disable this optimization. - * * @param forceDefaults true always serializes default values */ forceDefaults(forceDefaults: boolean): void { this._forceDefaults = forceDefaults } + /** Return the current contents as an {@link ArrayBuffer}. */ toArrayBuffer(): ArrayBuffer { return this.bb.view.buffer.slice(this.bb.position, this.bb.position + this.offset()) } + /** Ensure alignment of the next field, and grow the byte buffer if needed. */ prep(size: number, additionalBytes: number): void { if (size > this.minAlignment) { this.minAlignment = size @@ -300,66 +304,98 @@ export class Builder { this.pad(alignSize) } + /** Add padding to the backing {@link ByteBuffer} for alignment purposes. */ pad(byteSize: number): void { for (let i = 0; i < byteSize; i++) { this.bb.view.setInt8(--this.space, 0) } } + /** + * Write a signed 8-bit integer and update the buffer position. + * Prefer {@link addInt8} which ensures alignment as well. + */ writeInt8(value: number): void { this.bb.view.setInt8((this.space -= 1), value) } + /** + * Write a signed 16-bit integer and update the buffer position. + * Prefer {@link addInt16} which ensures alignment as well. + */ writeInt16(value: number): void { this.bb.view.setInt16((this.space -= 2), value, true) } + /** + * Write a signed 32-bit integer and update the buffer position. + * Prefer {@link addInt32} which ensures alignment as well. + */ writeInt32(value: number): void { this.bb.view.setInt32((this.space -= 4), value, true) } + /** + * Write a signed 64-bit integer and update the buffer position. + * Prefer {@link addInt64} which ensures alignment as well. + */ writeInt64(value: bigint): void { this.bb.view.setBigInt64((this.space -= 8), value, true) } + /** + * Write a 32-bit IEEE754 floating point number and update the buffer position. + * Prefer {@link addFloat32} which ensures alignment as well. + */ writeFloat32(value: number): void { this.bb.view.setFloat32((this.space -= 4), value, true) } + /** + * Write a 64-bit IEEE754 floating point number and update the buffer position. + * Prefer {@link addFloat64} which ensures alignment as well. + */ writeFloat64(value: number): void { this.bb.view.setFloat64((this.space -= 8), value, true) } + /** Ensure alignment and write a signed 8-bit integer and update the buffer position. */ addInt8(value: number): void { this.prep(1, 0) this.writeInt8(value) } + /** Ensure alignment and write a signed 16-bit integer and update the buffer position. */ addInt16(value: number): void { this.prep(2, 0) this.writeInt16(value) } + /** Ensure alignment and write a signed 32-bit integer and update the buffer position. */ addInt32(value: number): void { this.prep(4, 0) this.writeInt32(value) } + /** Ensure alignment and write a signed 64-bit integer and update the buffer position. */ addInt64(value: bigint): void { this.prep(8, 0) this.writeInt64(value) } + /** Ensure alignment and write a 32-bit IEEE754 floating point number and update the buffer position. */ addFloat32(value: number): void { this.prep(4, 0) this.writeFloat32(value) } + /** Ensure alignment and write a 64-bit IEEE754 floating point number and update the buffer position. */ addFloat64(value: number): void { this.prep(8, 0) this.writeFloat64(value) } + /** TODO: Add docs */ addFieldInt8(voffset: number, value: number, defaultValue: number | null): void { if (this._forceDefaults || value != defaultValue) { this.addInt8(value) @@ -367,6 +403,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldInt16(voffset: number, value: number, defaultValue: number | null): void { if (this._forceDefaults || value != defaultValue) { this.addInt16(value) @@ -374,6 +411,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldInt32(voffset: number, value: number, defaultValue: number | null): void { if (this._forceDefaults || value != defaultValue) { this.addInt32(value) @@ -381,6 +419,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldInt64(voffset: number, value: bigint, defaultValue: bigint | null): void { if (this._forceDefaults || value !== defaultValue) { this.addInt64(value) @@ -388,6 +427,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldFloat32(voffset: number, value: number, defaultValue: number | null): void { if (this._forceDefaults || value != defaultValue) { this.addFloat32(value) @@ -395,6 +435,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldFloat64(voffset: number, value: number, defaultValue: number | null): void { if (this._forceDefaults || value != defaultValue) { this.addFloat64(value) @@ -402,6 +443,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldOffset( voffset: number, value: Offset, @@ -413,6 +455,7 @@ export class Builder { } } + /** TODO: Add docs */ addFieldStruct( voffset: number, value: Offset, @@ -424,26 +467,34 @@ export class Builder { } } + /** TODO: Add docs */ nested(obj: AnyOffset): void { if (obj != this.offset()) { throw new TypeError('FlatBuffers: struct must be serialized inline.') } } + /** Assert that the builder is not already serializing an object when beginning object serialization. */ notNested(): void { if (this.isNested) { throw new TypeError('FlatBuffers: object serialization must not be nested.') } } + /** TODO: Add docs */ slot(voffset: number): void { if (this.vtable !== null) this.vtable[voffset] = this.offset() } + /** Return the offset of the current filled content of the backing {@link ByteBuffer}. */ offset(): Offset { return (this.bb.view.byteLength - this.space) as Offset } + /** + * Grow the internal {@link ByteBuffer}. + * Only call this if there is not enough space for the next write. + */ static growByteBuffer(bb: ByteBuffer): void { const oldBufSize = bb.view.byteLength // Ensure we don't grow beyond what fits in an int. @@ -457,11 +508,13 @@ export class Builder { bb.view = new DataView(newBuffer) } + /** Write an offset to a given field. */ addOffset(offset: AnyOffset): void { this.prep(SIZEOF_INT, 0) // Ensure alignment is already done. this.writeInt32(this.offset() - offset + SIZEOF_INT) } + /** TODO: Add docs */ startObject(numfields: number): void { this.notNested() if (this.vtable == null) { @@ -475,6 +528,7 @@ export class Builder { this.objectStart = this.offset() } + /** TODO: Add docs */ endObject(): Offset { if (this.vtable == null || !this.isNested) { throw new globalThis.Error('FlatBuffers: endObject called without startObject') @@ -536,6 +590,7 @@ export class Builder { return vtableloc as Offset } + /** Finalize a message to be ready for sending. */ finish( rootTable: AnyOffset, fileIdentifier?: string, @@ -560,11 +615,13 @@ export class Builder { return this } + /** TODO: Add docs */ finishSizePrefixed(rootTable: AnyOffset, fileIdentifier?: string): Builder { this.finish(rootTable, fileIdentifier, true) return this } + /** TODO: Add docs */ requiredField(table: AnyOffset, field: number): void { const tableStart = this.bb.view.byteLength - table const vtableStart = tableStart - this.bb.view.getInt32(tableStart, true) @@ -577,6 +634,7 @@ export class Builder { } } + /** Initialize buffer state for adding vector elements. */ startVector(elemSize: number, numElems: number, alignment: number): void { this.notNested() this.vectorNumElems = numElems @@ -584,11 +642,13 @@ export class Builder { this.prep(alignment, elemSize * numElems) // Just in case alignment > int. } + /** Finish buffer state after having added all vector elements. */ endVector(): Offset { this.writeInt32(this.vectorNumElems) return this.offset() } + /** Add a shared string to this buffer. */ createSharedString(s: T): Offset { if (!s) { return 0 as Offset @@ -605,6 +665,7 @@ export class Builder { return offset as Offset } + /** Add a string to this buffer. */ createString(s: string | Uint8Array | ArrayBuffer | null | undefined): Offset { if (s === null || s === undefined) return Null let utf8: string | Uint8Array | number[] @@ -624,6 +685,7 @@ export class Builder { return this.endVector() } + /** TODO: Add docs */ createObjectOffset( obj: T extends string | null ? T : IGeneratedObject, ): Offset { @@ -632,8 +694,9 @@ export class Builder { else return obj.pack(this) as Offset } + /** TODO: Add docs */ createObjectOffsetList( - list: (T extends string ? string : IGeneratedObject)[], + list: readonly (T extends string ? string : IGeneratedObject)[], ): Offset[] { const ret: number[] = [] for (let i = 0; i < list.length; ++i) { @@ -647,8 +710,9 @@ export class Builder { return ret as Offset[] } + /** TODO: Add docs */ createStructOffsetList( - list: (T extends string ? string : IGeneratedObject)[], + list: readonly (T extends string ? string : IGeneratedObject)[], startFunc: (builder: Builder, length: number) => void, ): Offset { startFunc(this, list.length) @@ -657,14 +721,17 @@ export class Builder { } } +/** An {@link ArrayBuffer} wrapper with added utility methods. */ export class ByteBuffer { position = 0 view: DataView + /** TODO: Add docs */ constructor(buffer: ArrayBufferLike) { this.view = new DataView(buffer) } + /** TODO: Add docs */ offset(bbPos: number, vtableOffset: number): Offset { const vtable = bbPos - this.view.getInt32(bbPos, true) return ( @@ -673,12 +740,14 @@ export class ByteBuffer { : 0) as Offset } + /** TODO: Add docs */ union(t: Table, offset: number): Table { t.bbPos = offset + this.view.getInt32(offset, true) t.bb = this return t } + /** TODO: Add docs */ rawMessage(offset: number): ArrayBuffer { offset += this.view.getInt32(offset, true) const length = this.view.getInt32(offset, true) @@ -686,20 +755,24 @@ export class ByteBuffer { return this.view.buffer.slice(offset, offset + length) } + /** Extract a string from the given offset. */ message(offset: number): string { return TEXT_DECODER.decode(this.rawMessage(offset)) } + /** Get the offset to the start of the referenced value, given the position of the reference. */ indirect(offset: number | Offset): Offset { return (offset + this.view.getInt32(offset, true)) as Offset } + /** Get the offset to the start of the vector, given the offset to the entire vector. */ vector(offset: number | Offset): Offset { return (offset + this.view.getInt32(offset, true) + SIZEOF_INT) as Offset // data starts after the length } - vectorLength(offset: number | AnyOffset): Offset { - return this.view.getInt32(offset + this.view.getInt32(offset, true), true) as Offset + /** Read the length of the vector, given the offset to the entire vector. */ + vectorLength(offset: number | AnyOffset): number { + return this.view.getInt32(offset + this.view.getInt32(offset, true), true) } } @@ -750,65 +823,80 @@ export enum ErrorPayload { export type AnyErrorPayload = None | ReadOutOfBoundsError +/** TODO: Add docs */ export class InboundMessage implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): InboundMessage { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsInboundMessage(bb: ByteBuffer, obj?: InboundMessage): InboundMessage { return (obj ?? new InboundMessage()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsInboundMessage(bb: ByteBuffer, obj?: InboundMessage): InboundMessage { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new InboundMessage()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ messageId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** TODO: Add docs */ correlationId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** TODO: Add docs */ payloadType(): InboundPayload { const offset = this.bb.offset(this.bbPos, 8) return offset ? this.bb.view.getUint8(this.bbPos + offset) : InboundPayload.NONE } + /** TODO: Add docs */ payload(obj: T): T | null { const offset = this.bb.offset(this.bbPos, 10) - // @ts-expect-error + // @ts-expect-error This is UNSAFE. Care must be taken to ensure `obj` is + // of the corresponding type for the `payloadType` field. return offset ? this.bb.union(obj, this.bbPos + offset) : null } + /** TODO: Add docs */ static startInboundMessage(builder: Builder) { builder.startObject(4) } + /** TODO: Add docs */ static addMessageId(builder: Builder, messageIdOffset: Offset) { builder.addFieldStruct(0, messageIdOffset, Null) } + /** TODO: Add docs */ static addCorrelationId(builder: Builder, correlationIdOffset: Offset) { builder.addFieldStruct(1, correlationIdOffset, Null) } + /** TODO: Add docs */ static addPayloadType(builder: Builder, payloadType: InboundPayload) { builder.addFieldInt8(2, payloadType, InboundPayload.NONE) } + /** TODO: Add docs */ static addPayload(builder: Builder, payloadOffset: Offset) { builder.addFieldOffset(3, payloadOffset, Null) } + /** TODO: Add docs */ static endInboundMessage(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // messageId @@ -816,6 +904,7 @@ export class InboundMessage implements Table { return offset } + /** TODO: Add docs */ static createInboundMessage( builder: Builder, createMessageId: CreateOffset, @@ -832,15 +921,18 @@ export class InboundMessage implements Table { } } +/** TODO: Add docs */ export class OutboundMessage implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): OutboundMessage { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsOutboundMessage(bb: ByteBuffer, obj?: OutboundMessage): OutboundMessage { return (obj ?? new OutboundMessage()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -848,6 +940,7 @@ export class OutboundMessage implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsOutboundMessage( bb: ByteBuffer, obj?: OutboundMessage, @@ -859,47 +952,58 @@ export class OutboundMessage implements Table { ) } + /** Get the `messageId` field of this message. */ messageId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** Get the `correlationId` field of this message. */ correlationId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** Get the `payloadType` field of this message. */ payloadType(): OutboundPayload { const offset = this.bb.offset(this.bbPos, 8) return offset ? this.bb.view.getUint8(this.bbPos + offset) : OutboundPayload.NONE } + /** Get the `payload` field of this message. */ payload(obj: T): T | null { const offset = this.bb.offset(this.bbPos, 10) - // @ts-expect-error + // @ts-expect-error This is UNSAFE. Care must be taken to ensure `obj` is + // of the corresponding type for the `payloadType` field. return offset ? this.bb.union(obj, this.bbPos + offset) : null } + /** Start encoding this struct in the builder. */ static startOutboundMessage(builder: Builder) { builder.startObject(4) } + /** Add a `messageId` field to the given builder given its offset. */ static addMessageId(builder: Builder, messageIdOffset: Offset) { builder.addFieldStruct(0, messageIdOffset, Null) } + /** Add a `correlationId` field to the given builder given its offset. */ static addCorrelationId(builder: Builder, correlationIdOffset: Offset) { builder.addFieldStruct(1, correlationIdOffset, Null) } + /** Add a `payloadType` field to the given builder given its offset. */ static addPayloadType(builder: Builder, payloadType: OutboundPayload) { builder.addFieldInt8(2, payloadType, OutboundPayload.NONE) } + /** Add a `payload` field to the given builder given its offset. */ static addPayload(builder: Builder, payloadOffset: Offset) { builder.addFieldOffset(3, payloadOffset, Null) } + /** Finish encoding this struct in the builder. */ static endOutboundMessage(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // messageId @@ -907,6 +1011,7 @@ export class OutboundMessage implements Table { return offset } + /** Encode this struct in the builder, given the values of all fields. */ static createOutboundMessage( builder: Builder, createMessageId: CreateOffset, @@ -914,32 +1019,37 @@ export class OutboundMessage implements Table { payloadType: OutboundPayload, payloadOffset: Offset, ): Offset { - OutboundMessage.startOutboundMessage(builder) - OutboundMessage.addMessageId(builder, createMessageId?.(builder) ?? Null) - OutboundMessage.addCorrelationId(builder, createCorrelationId?.(builder) ?? Null) - OutboundMessage.addPayloadType(builder, payloadType) - OutboundMessage.addPayload(builder, payloadOffset) - return OutboundMessage.endOutboundMessage(builder) + this.startOutboundMessage(builder) + this.addMessageId(builder, createMessageId?.(builder) ?? Null) + this.addCorrelationId(builder, createCorrelationId?.(builder) ?? Null) + this.addPayloadType(builder, payloadType) + this.addPayload(builder, payloadOffset) + return this.endOutboundMessage(builder) } } +/** TODO: Add docs */ export class EnsoUUID implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): EnsoUUID { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ leastSigBits(): bigint { return this.bb.view.getBigUint64(this.bbPos, true) } + /** TODO: Add docs */ mostSigBits(): bigint { return this.bb.view.getBigUint64(this.bbPos + 8, true) } + /** TODO: Add docs */ static createEnsoUUID( builder: Builder, leastSigBits: bigint, @@ -952,76 +1062,93 @@ export class EnsoUUID implements Table { } } +/** A struct representing a failed operation. */ export class Error implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): Error { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsError(bb: ByteBuffer, obj?: Error): Error { return (obj ?? new Error()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsError(bb: ByteBuffer, obj?: Error): Error { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new Error()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** Get the `code` field of this message. */ code(): number { const offset = this.bb.offset(this.bbPos, 4) return offset ? this.bb.view.getInt32(this.bbPos + offset, true) : 0 } + /** Get the `message` field of this message, as an {@link ArrayBuffer}. */ rawMessage(): ArrayBuffer | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.rawMessage(this.bbPos + offset) : null } + /** Get the `message` field of this message. */ message(): string | null { const rawMessage = this.rawMessage() return rawMessage ? TEXT_DECODER.decode(rawMessage) : null } + /** Get the `dataType` field of this message. */ dataType(): ErrorPayload { const offset = this.bb.offset(this.bbPos, 8) return offset ? this.bb.view.getUint8(this.bbPos + offset) : ErrorPayload.NONE } + /** Get the `data` field of this message. */ data(obj: T): T | null { const offset = this.bb.offset(this.bbPos, 10) - // @ts-expect-error + // @ts-expect-error This is UNSAFE. Care must be taken to ensure the`obj` is + // of the corresponding type for the `dataType` field. return offset ? this.bb.union(obj, this.bbPos + offset) : null } + /** TODO: Add docs */ static startError(builder: Builder) { builder.startObject(4) } + /** TODO: Add docs */ static addCode(builder: Builder, code: number) { builder.addFieldInt32(0, code, 0) } + /** TODO: Add docs */ static addMessage(builder: Builder, messageOffset: Offset) { builder.addFieldOffset(1, messageOffset, Null) } + /** TODO: Add docs */ static addDataType(builder: Builder, dataType: ErrorPayload) { builder.addFieldInt8(2, dataType, ErrorPayload.NONE) } + /** TODO: Add docs */ static addData(builder: Builder, dataOffset: Offset) { builder.addFieldOffset(3, dataOffset, Null) } + /** TODO: Add docs */ static endError(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 6) // message return offset } + /** TODO: Add docs */ static createError( builder: Builder, code: number, @@ -1038,15 +1165,18 @@ export class Error implements Table { } } +/** TODO: Add docs */ export class ReadOutOfBoundsError implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): ReadOutOfBoundsError { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsReadOutOfBoundsError( bb: ByteBuffer, obj?: ReadOutOfBoundsError, @@ -1057,6 +1187,7 @@ export class ReadOutOfBoundsError implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsReadOutOfBoundsError( bb: ByteBuffer, obj?: ReadOutOfBoundsError, @@ -1068,24 +1199,29 @@ export class ReadOutOfBoundsError implements Table { ) } + /** TODO: Add docs */ fileLength(): bigint { const offset = this.bb.offset(this.bbPos, 4) return offset ? this.bb.view.getBigUint64(this.bbPos + offset, true) : 0n } + /** TODO: Add docs */ static startReadOutOfBoundsError(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addFileLength(builder: Builder, fileLength: bigint) { builder.addFieldInt64(0, fileLength, 0n) } + /** TODO: Add docs */ static endReadOutOfBoundsError(builder: Builder): Offset { const offset = builder.endObject() return offset } + /** TODO: Add docs */ static createReadOutOfBoundsError( builder: Builder, fileLength: bigint, @@ -1096,9 +1232,11 @@ export class ReadOutOfBoundsError implements Table { } } +/** TODO: Add docs */ export class None implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): None { this.bbPos = i this.bb = bb @@ -1106,48 +1244,58 @@ export class None implements Table { } } +/** TODO: Add docs */ export class Success implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): Success { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsSuccess(bb: ByteBuffer, obj?: Success): Success { return (obj ?? new Success()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsSuccess(bb: ByteBuffer, obj?: Success): Success { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new Success()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static startSuccess(builder: Builder) { builder.startObject(0) } + /** TODO: Add docs */ static endSuccess(builder: Builder): Offset { const offset = builder.endObject() return offset } + /** TODO: Add docs */ static createSuccess(builder: Builder): Offset { Success.startSuccess(builder) return Success.endSuccess(builder) } } +/** TODO: Add docs */ export class InitSessionCommand implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): InitSessionCommand { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsInitSessionCommand(bb: ByteBuffer, obj?: InitSessionCommand): InitSessionCommand { return (obj ?? new InitSessionCommand()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1155,6 +1303,7 @@ export class InitSessionCommand implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsInitSessionCommand( bb: ByteBuffer, obj?: InitSessionCommand, @@ -1166,25 +1315,30 @@ export class InitSessionCommand implements Table { ) } + /** TODO: Add docs */ identifier(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** TODO: Add docs */ static startInitSessionCommand(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addIdentifier(builder: Builder, identifierOffset: Offset) { builder.addFieldStruct(0, identifierOffset, Null) } + /** TODO: Add docs */ static endInitSessionCommand(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // identifier return offset } + /** TODO: Add docs */ static createInitSessionCommand( builder: Builder, createIdentifier: CreateOffset, @@ -1195,15 +1349,18 @@ export class InitSessionCommand implements Table { } } +/** TODO: Add docs */ export class VisualizationContext implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): VisualizationContext { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsVisualizationContext( bb: ByteBuffer, obj?: VisualizationContext, @@ -1214,6 +1371,7 @@ export class VisualizationContext implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsVisualizationContext( bb: ByteBuffer, obj?: VisualizationContext, @@ -1225,37 +1383,45 @@ export class VisualizationContext implements Table { ) } + /** TODO: Add docs */ visualizationId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** TODO: Add docs */ contextId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** TODO: Add docs */ expressionId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 8) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb!) : null } + /** TODO: Add docs */ static startVisualizationContext(builder: Builder) { builder.startObject(3) } + /** TODO: Add docs */ static addVisualizationId(builder: Builder, visualizationIdOffset: Offset) { builder.addFieldStruct(0, visualizationIdOffset, Null) } + /** TODO: Add docs */ static addContextId(builder: Builder, contextIdOffset: Offset) { builder.addFieldStruct(1, contextIdOffset, Null) } + /** TODO: Add docs */ static addExpressionId(builder: Builder, expressionIdOffset: Offset) { builder.addFieldStruct(2, expressionIdOffset, Null) } + /** TODO: Add docs */ static endVisualizationContext(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // visualizationId @@ -1264,6 +1430,7 @@ export class VisualizationContext implements Table { return offset } + /** TODO: Add docs */ static createVisualizationContext( builder: Builder, createVisualizationId: CreateOffset, @@ -1278,15 +1445,18 @@ export class VisualizationContext implements Table { } } +/** TODO: Add docs */ export class VisualizationUpdate implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): VisualizationUpdate { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsVisualizationUpdate( bb: ByteBuffer, obj?: VisualizationUpdate, @@ -1297,6 +1467,7 @@ export class VisualizationUpdate implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsVisualizationUpdate( bb: ByteBuffer, obj?: VisualizationUpdate, @@ -1308,6 +1479,7 @@ export class VisualizationUpdate implements Table { ) } + /** TODO: Add docs */ visualizationContext(obj?: VisualizationContext): VisualizationContext | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -1315,16 +1487,19 @@ export class VisualizationUpdate implements Table { : null } + /** TODO: Add docs */ data(index: number): number | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.view.getUint8(this.bb.vector(this.bbPos + offset) + index) : 0 } + /** TODO: Add docs */ dataLength(): number { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ dataArray(): Uint8Array | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? @@ -1336,15 +1511,18 @@ export class VisualizationUpdate implements Table { : null } + /** TODO: Add docs */ dataString(): string | null { const buffer = this.dataArray() return buffer != null ? TEXT_DECODER.decode(buffer) : null } + /** TODO: Add docs */ static startVisualizationUpdate(builder: Builder) { builder.startObject(2) } + /** TODO: Add docs */ static addVisualizationContext( builder: Builder, visualizationContextOffset: Offset, @@ -1352,10 +1530,12 @@ export class VisualizationUpdate implements Table { builder.addFieldOffset(0, visualizationContextOffset, Null) } + /** TODO: Add docs */ static addData(builder: Builder, dataOffset: Offset) { builder.addFieldOffset(1, dataOffset, Null) } + /** TODO: Add docs */ static createDataVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. @@ -1365,10 +1545,12 @@ export class VisualizationUpdate implements Table { return builder.endVector() } + /** TODO: Add docs */ static startDataVector(builder: Builder, numElems: number) { builder.startVector(1, numElems, 1) } + /** TODO: Add docs */ static endVisualizationUpdate(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // visualizationContext @@ -1376,6 +1558,7 @@ export class VisualizationUpdate implements Table { return offset } + /** TODO: Add docs */ static createVisualizationUpdate( builder: Builder, visualizationContextOffset: Offset, @@ -1388,29 +1571,35 @@ export class VisualizationUpdate implements Table { } } +/** TODO: Add docs */ export class Path implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): Path { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsPath(bb: ByteBuffer, obj?: Path): Path { return (obj ?? new Path()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsPath(bb: ByteBuffer, obj?: Path): Path { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new Path()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ rootId(obj?: EnsoUUID): EnsoUUID | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new EnsoUUID()).init(this.bbPos + offset, this.bb) : null } + /** TODO: Add docs */ rawSegments(index: number): ArrayBuffer { const offset = this.bb.offset(this.bbPos, 6) return offset ? @@ -1418,27 +1607,33 @@ export class Path implements Table { : new Uint8Array() } + /** TODO: Add docs */ segments(index: number): string { return TEXT_DECODER.decode(this.rawSegments(index)) } + /** TODO: Add docs */ segmentsLength(): number { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ static startPath(builder: Builder) { builder.startObject(2) } + /** TODO: Add docs */ static addRootId(builder: Builder, rootIdOffset: Offset) { builder.addFieldStruct(0, rootIdOffset, Null) } + /** TODO: Add docs */ static addSegments(builder: Builder, segmentsOffset: Offset) { builder.addFieldOffset(1, segmentsOffset, Null) } + /** TODO: Add docs */ static createSegmentsVector( builder: Builder, data: Offset[] | Offset[], @@ -1451,15 +1646,18 @@ export class Path implements Table { return builder.endVector() } + /** TODO: Add docs */ static startSegmentsVector(builder: Builder, numElems: number) { builder.startVector(4, numElems, 4) } + /** TODO: Add docs */ static endPath(builder: Builder): Offset { const offset = builder.endObject() return offset } + /** TODO: Add docs */ static createPath( builder: Builder, createRootId: CreateOffset, @@ -1472,15 +1670,18 @@ export class Path implements Table { } } +/** TODO: Add docs */ export class WriteFileCommand implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): WriteFileCommand { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsWriteFileCommand(bb: ByteBuffer, obj?: WriteFileCommand): WriteFileCommand { return (obj ?? new WriteFileCommand()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1488,6 +1689,7 @@ export class WriteFileCommand implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsWriteFileCommand( bb: ByteBuffer, obj?: WriteFileCommand, @@ -1499,21 +1701,25 @@ export class WriteFileCommand implements Table { ) } + /** TODO: Add docs */ path(obj?: Path): Path | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new Path()).init(this.bb.indirect(this.bbPos + offset), this.bb!) : null } + /** TODO: Add docs */ contents(index: number): number | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.view.getUint8(this.bb.vector(this.bbPos + offset) + index) : 0 } + /** TODO: Add docs */ contentsLength(): number { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ contentsArray(): Uint8Array | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? @@ -1525,18 +1731,22 @@ export class WriteFileCommand implements Table { : null } + /** TODO: Add docs */ static startWriteFileCommand(builder: Builder) { builder.startObject(2) } + /** TODO: Add docs */ static addPath(builder: Builder, pathOffset: Offset) { builder.addFieldOffset(0, pathOffset, Null) } + /** TODO: Add docs */ static addContents(builder: Builder, contentsOffset: Offset) { builder.addFieldOffset(1, contentsOffset, Null) } + /** TODO: Add docs */ static createContentsVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. @@ -1546,15 +1756,18 @@ export class WriteFileCommand implements Table { return builder.endVector() } + /** TODO: Add docs */ static startContentsVector(builder: Builder, numElems: number) { builder.startVector(1, numElems, 1) } + /** TODO: Add docs */ static endWriteFileCommand(builder: Builder): Offset { const offset = builder.endObject() return offset } + /** TODO: Add docs */ static createWriteFileCommand( builder: Builder, pathOffset: Offset, @@ -1567,15 +1780,18 @@ export class WriteFileCommand implements Table { } } +/** TODO: Add docs */ export class ReadFileCommand implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): ReadFileCommand { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsReadFileCommand(bb: ByteBuffer, obj?: ReadFileCommand): ReadFileCommand { return (obj ?? new ReadFileCommand()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1583,6 +1799,7 @@ export class ReadFileCommand implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsReadFileCommand( bb: ByteBuffer, obj?: ReadFileCommand, @@ -1594,24 +1811,29 @@ export class ReadFileCommand implements Table { ) } + /** TODO: Add docs */ path(obj?: Path): Path | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new Path()).init(this.bb.indirect(this.bbPos + offset), this.bb!) : null } + /** TODO: Add docs */ static startReadFileCommand(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addPath(builder: Builder, pathOffset: Offset) { builder.addFieldOffset(0, pathOffset, Null) } + /** TODO: Add docs */ static endReadFileCommand(builder: Builder): Offset { const offset = builder.endObject() return offset } + /** TODO: Add docs */ static createReadFileCommand( builder: Builder, pathOffset: Offset, @@ -1622,15 +1844,18 @@ export class ReadFileCommand implements Table { } } +/** TODO: Add docs */ export class FileContentsReply implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): FileContentsReply { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsFileContentsReply(bb: ByteBuffer, obj?: FileContentsReply): FileContentsReply { return (obj ?? new FileContentsReply()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1638,6 +1863,7 @@ export class FileContentsReply implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsFileContentsReply( bb: ByteBuffer, obj?: FileContentsReply, @@ -1649,16 +1875,19 @@ export class FileContentsReply implements Table { ) } + /** TODO: Add docs */ contents(index: number): number | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? this.bb.view.getUint8(this.bb.vector(this.bbPos + offset) + index) : 0 } + /** TODO: Add docs */ contentsLength(): number { const offset = this.bb.offset(this.bbPos, 4) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ contentsArray(): Uint8Array | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -1670,14 +1899,17 @@ export class FileContentsReply implements Table { : null } + /** TODO: Add docs */ static startFileContentsReply(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addContents(builder: Builder, contentsOffset: Offset) { builder.addFieldOffset(0, contentsOffset, Null) } + /** TODO: Add docs */ static createContentsVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. @@ -1687,15 +1919,18 @@ export class FileContentsReply implements Table { return builder.endVector() } + /** TODO: Add docs */ static startContentsVector(builder: Builder, numElems: number) { builder.startVector(1, numElems, 1) } + /** TODO: Add docs */ static endFileContentsReply(builder: Builder): Offset { const offset = builder.endObject() return offset } + /** TODO: Add docs */ static createFileContentsReply( builder: Builder, contentsOffset: Offset, @@ -1706,15 +1941,18 @@ export class FileContentsReply implements Table { } } +/** TODO: Add docs */ export class WriteBytesCommand implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): WriteBytesCommand { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsWriteBytesCommand(bb: ByteBuffer, obj?: WriteBytesCommand): WriteBytesCommand { return (obj ?? new WriteBytesCommand()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1722,6 +1960,7 @@ export class WriteBytesCommand implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsWriteBytesCommand( bb: ByteBuffer, obj?: WriteBytesCommand, @@ -1733,31 +1972,37 @@ export class WriteBytesCommand implements Table { ) } + /** TODO: Add docs */ path(obj?: Path): Path | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new Path()).init(this.bb.indirect(this.bbPos + offset), this.bb!) : null } + /** TODO: Add docs */ byteOffset(): bigint { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.view.getBigUint64(this.bbPos + offset, true) : 0n } + /** TODO: Add docs */ overwriteExisting(): boolean { const offset = this.bb.offset(this.bbPos, 8) return offset ? !!this.bb.view.getInt8(this.bbPos + offset) : false } + /** TODO: Add docs */ bytes(index: number): number | null { const offset = this.bb.offset(this.bbPos, 10) return offset ? this.bb.view.getUint8(this.bb.vector(this.bbPos + offset) + index) : 0 } + /** TODO: Add docs */ bytesLength(): number { const offset = this.bb.offset(this.bbPos, 10) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ bytesArray(): Uint8Array | null { const offset = this.bb.offset(this.bbPos, 10) return offset ? @@ -1769,26 +2014,32 @@ export class WriteBytesCommand implements Table { : null } + /** TODO: Add docs */ static startWriteBytesCommand(builder: Builder) { builder.startObject(4) } + /** TODO: Add docs */ static addPath(builder: Builder, pathOffset: Offset) { builder.addFieldOffset(0, pathOffset, Null) } + /** TODO: Add docs */ static addByteOffset(builder: Builder, byteOffset: bigint) { builder.addFieldInt64(1, byteOffset, 0n) } + /** TODO: Add docs */ static addOverwriteExisting(builder: Builder, overwriteExisting: boolean) { builder.addFieldInt8(2, +overwriteExisting, +false) } + /** TODO: Add docs */ static addBytes(builder: Builder, bytesOffset: Offset) { builder.addFieldOffset(3, bytesOffset, Null) } + /** TODO: Add docs */ static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. @@ -1798,10 +2049,12 @@ export class WriteBytesCommand implements Table { return builder.endVector() } + /** TODO: Add docs */ static startBytesVector(builder: Builder, numElems: number) { builder.startVector(1, numElems, 1) } + /** TODO: Add docs */ static endWriteBytesCommand(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // path @@ -1809,6 +2062,7 @@ export class WriteBytesCommand implements Table { return offset } + /** TODO: Add docs */ static createWriteBytesCommand( builder: Builder, pathOffset: Offset, @@ -1825,15 +2079,18 @@ export class WriteBytesCommand implements Table { } } +/** TODO: Add docs */ export class WriteBytesReply implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): WriteBytesReply { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsWriteBytesReply(bb: ByteBuffer, obj?: WriteBytesReply): WriteBytesReply { return (obj ?? new WriteBytesReply()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1841,6 +2098,7 @@ export class WriteBytesReply implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsWriteBytesReply( bb: ByteBuffer, obj?: WriteBytesReply, @@ -1852,6 +2110,7 @@ export class WriteBytesReply implements Table { ) } + /** TODO: Add docs */ checksum(obj?: EnsoDigest): EnsoDigest | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -1859,20 +2118,24 @@ export class WriteBytesReply implements Table { : null } + /** TODO: Add docs */ static startWriteBytesReply(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addChecksum(builder: Builder, checksumOffset: Offset) { builder.addFieldOffset(0, checksumOffset, Null) } + /** TODO: Add docs */ static endWriteBytesReply(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // checksum return offset } + /** TODO: Add docs */ static createWriteBytesReply( builder: Builder, checksumOffset: Offset, @@ -1883,15 +2146,18 @@ export class WriteBytesReply implements Table { } } +/** TODO: Add docs */ export class ReadBytesCommand implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): ReadBytesCommand { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsReadBytesCommand(bb: ByteBuffer, obj?: ReadBytesCommand): ReadBytesCommand { return (obj ?? new ReadBytesCommand()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -1899,6 +2165,7 @@ export class ReadBytesCommand implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsReadBytesCommand( bb: ByteBuffer, obj?: ReadBytesCommand, @@ -1910,6 +2177,7 @@ export class ReadBytesCommand implements Table { ) } + /** TODO: Add docs */ segment(obj?: FileSegment): FileSegment | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -1917,20 +2185,24 @@ export class ReadBytesCommand implements Table { : null } + /** TODO: Add docs */ static startReadBytesCommand(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addSegment(builder: Builder, segmentOffset: Offset) { builder.addFieldOffset(0, segmentOffset, Null) } + /** TODO: Add docs */ static endReadBytesCommand(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // segment return offset } + /** TODO: Add docs */ static createReadBytesCommand( builder: Builder, segmentOffset: Offset, @@ -1941,24 +2213,29 @@ export class ReadBytesCommand implements Table { } } +/** TODO: Add docs */ export class ReadBytesReply implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): ReadBytesReply { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsReadBytesReply(bb: ByteBuffer, obj?: ReadBytesReply): ReadBytesReply { return (obj ?? new ReadBytesReply()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsReadBytesReply(bb: ByteBuffer, obj?: ReadBytesReply): ReadBytesReply { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new ReadBytesReply()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ checksum(obj?: EnsoDigest): EnsoDigest | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -1966,16 +2243,19 @@ export class ReadBytesReply implements Table { : null } + /** TODO: Add docs */ bytes(index: number): number | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.view.getUint8(this.bb.vector(this.bbPos + offset) + index) : 0 } + /** TODO: Add docs */ bytesLength(): number { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ bytesArray(): Uint8Array | null { const offset = this.bb.offset(this.bbPos, 6) return offset ? @@ -1987,18 +2267,22 @@ export class ReadBytesReply implements Table { : null } + /** TODO: Add docs */ static startReadBytesReply(builder: Builder) { builder.startObject(2) } + /** TODO: Add docs */ static addChecksum(builder: Builder, checksumOffset: Offset) { builder.addFieldOffset(0, checksumOffset, Null) } + /** TODO: Add docs */ static addBytes(builder: Builder, bytesOffset: Offset) { builder.addFieldOffset(1, bytesOffset, Null) } + /** TODO: Add docs */ static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. @@ -2008,10 +2292,12 @@ export class ReadBytesReply implements Table { return builder.endVector() } + /** TODO: Add docs */ static startBytesVector(builder: Builder, numElems: number) { builder.startVector(1, numElems, 1) } + /** TODO: Add docs */ static endReadBytesReply(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // checksum @@ -2019,6 +2305,7 @@ export class ReadBytesReply implements Table { return offset } + /** TODO: Add docs */ static createReadBytesReply( builder: Builder, checksumOffset: Offset, @@ -2031,15 +2318,18 @@ export class ReadBytesReply implements Table { } } +/** TODO: Add docs */ export class ChecksumBytesCommand implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): ChecksumBytesCommand { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsChecksumBytesCommand( bb: ByteBuffer, obj?: ChecksumBytesCommand, @@ -2050,6 +2340,7 @@ export class ChecksumBytesCommand implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsChecksumBytesCommand( bb: ByteBuffer, obj?: ChecksumBytesCommand, @@ -2061,6 +2352,7 @@ export class ChecksumBytesCommand implements Table { ) } + /** TODO: Add docs */ segment(obj?: FileSegment): FileSegment | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -2068,20 +2360,24 @@ export class ChecksumBytesCommand implements Table { : null } + /** TODO: Add docs */ static startChecksumBytesCommand(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addSegment(builder: Builder, segmentOffset: Offset) { builder.addFieldOffset(0, segmentOffset, Null) } + /** TODO: Add docs */ static endChecksumBytesCommand(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // segment return offset } + /** TODO: Add docs */ static createChecksumBytesCommand( builder: Builder, segmentOffset: Offset, @@ -2092,15 +2388,18 @@ export class ChecksumBytesCommand implements Table { } } +/** TODO: Add docs */ export class ChecksumBytesReply implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): ChecksumBytesReply { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsChecksumBytesReply(bb: ByteBuffer, obj?: ChecksumBytesReply): ChecksumBytesReply { return (obj ?? new ChecksumBytesReply()).init( bb.view.getInt32(bb.position, true) + bb.position, @@ -2108,6 +2407,7 @@ export class ChecksumBytesReply implements Table { ) } + /** TODO: Add docs */ static getSizePrefixedRootAsChecksumBytesReply( bb: ByteBuffer, obj?: ChecksumBytesReply, @@ -2119,6 +2419,7 @@ export class ChecksumBytesReply implements Table { ) } + /** TODO: Add docs */ checksum(obj?: EnsoDigest): EnsoDigest | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -2126,20 +2427,24 @@ export class ChecksumBytesReply implements Table { : null } + /** TODO: Add docs */ static startChecksumBytesReply(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addChecksum(builder: Builder, checksumOffset: Offset) { builder.addFieldOffset(0, checksumOffset, Null) } + /** TODO: Add docs */ static endChecksumBytesReply(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // checksum return offset } + /** TODO: Add docs */ static createChecksumBytesReply( builder: Builder, checksumOffset: Offset, @@ -2150,34 +2455,41 @@ export class ChecksumBytesReply implements Table { } } +/** TODO: Add docs */ export class EnsoDigest implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): EnsoDigest { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsEnsoDigest(bb: ByteBuffer, obj?: EnsoDigest): EnsoDigest { return (obj ?? new EnsoDigest()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsEnsoDigest(bb: ByteBuffer, obj?: EnsoDigest): EnsoDigest { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new EnsoDigest()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ bytes(index: number): number | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? this.bb.view.getUint8(this.bb.vector(this.bbPos + offset) + index) : 0 } + /** TODO: Add docs */ bytesLength(): number { const offset = this.bb.offset(this.bbPos, 4) return offset ? this.bb.vectorLength(this.bbPos + offset) : 0 } + /** TODO: Add docs */ bytesArray(): Uint8Array | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? @@ -2189,14 +2501,17 @@ export class EnsoDigest implements Table { : null } + /** TODO: Add docs */ static startEnsoDigest(builder: Builder) { builder.startObject(1) } + /** TODO: Add docs */ static addBytes(builder: Builder, bytesOffset: Offset) { builder.addFieldOffset(0, bytesOffset, Null) } + /** TODO: Add docs */ static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. @@ -2206,16 +2521,19 @@ export class EnsoDigest implements Table { return builder.endVector() } + /** TODO: Add docs */ static startBytesVector(builder: Builder, numElems: number) { builder.startVector(1, numElems, 1) } + /** TODO: Add docs */ static endEnsoDigest(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // bytes return offset } + /** TODO: Add docs */ static createEnsoDigest(builder: Builder, bytesOffset: Offset): Offset { EnsoDigest.startEnsoDigest(builder) EnsoDigest.addBytes(builder, bytesOffset) @@ -2223,61 +2541,74 @@ export class EnsoDigest implements Table { } } +/** TODO: Add docs */ export class FileSegment implements Table { bb!: ByteBuffer bbPos: number = 0 + /** TODO: Add docs */ init(i: number, bb: ByteBuffer): FileSegment { this.bbPos = i this.bb = bb return this } + /** TODO: Add docs */ static getRootAsFileSegment(bb: ByteBuffer, obj?: FileSegment): FileSegment { return (obj ?? new FileSegment()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ static getSizePrefixedRootAsFileSegment(bb: ByteBuffer, obj?: FileSegment): FileSegment { bb.position += SIZE_PREFIX_LENGTH return (obj ?? new FileSegment()).init(bb.view.getInt32(bb.position, true) + bb.position, bb) } + /** TODO: Add docs */ path(obj?: Path): Path | null { const offset = this.bb.offset(this.bbPos, 4) return offset ? (obj ?? new Path()).init(this.bb.indirect(this.bbPos + offset), this.bb!) : null } + /** TODO: Add docs */ byteOffset(): bigint { const offset = this.bb.offset(this.bbPos, 6) return offset ? this.bb.view.getBigUint64(this.bbPos + offset, true) : 0n } + /** TODO: Add docs */ length(): bigint { const offset = this.bb.offset(this.bbPos, 8) return offset ? this.bb.view.getBigUint64(this.bbPos + offset, true) : 0n } + /** TODO: Add docs */ static startFileSegment(builder: Builder) { builder.startObject(3) } + /** TODO: Add docs */ static addPath(builder: Builder, pathOffset: Offset) { builder.addFieldOffset(0, pathOffset, Null) } + /** TODO: Add docs */ static addByteOffset(builder: Builder, byteOffset: bigint) { builder.addFieldInt64(1, byteOffset, 0n) } + /** TODO: Add docs */ static addLength(builder: Builder, length: bigint) { builder.addFieldInt64(2, length, 0n) } + /** TODO: Add docs */ static endFileSegment(builder: Builder): Offset { const offset = builder.endObject() builder.requiredField(offset, 4) // path return offset } + /** TODO: Add docs */ static createFileSegment( builder: Builder, pathOffset: Offset, diff --git a/app/ydoc-shared/src/ensoFile.ts b/app/ydoc-shared/src/ensoFile.ts index d5703f3950e..28882f7ab4a 100644 --- a/app/ydoc-shared/src/ensoFile.ts +++ b/app/ydoc-shared/src/ensoFile.ts @@ -6,6 +6,7 @@ export interface EnsoFileParts { metadataJson: string | null } +/** Return the parts of a file, given its entire content. */ export function splitFileContents(content: string): EnsoFileParts { const splitPoint = content.lastIndexOf(META_TAG) if (splitPoint < 0) { @@ -23,6 +24,7 @@ export function splitFileContents(content: string): EnsoFileParts { return { code, idMapJson, metadataJson } } +/** Return the entire content of a file, given its parts. */ export function combineFileParts(parts: EnsoFileParts): string { const hasMeta = parts.idMapJson != null || parts.metadataJson != null if (hasMeta) { diff --git a/app/ydoc-shared/src/languageServer.ts b/app/ydoc-shared/src/languageServer.ts index e1403f50420..ea511a17a70 100644 --- a/app/ydoc-shared/src/languageServer.ts +++ b/app/ydoc-shared/src/languageServer.ts @@ -87,10 +87,12 @@ const RemoteRpcErrorSchema = z.object({ }) type RemoteRpcErrorParsed = z.infer +/** Payload for a {@linnk LsRpcError}. */ export class RemoteRpcError { code: ErrorCode message: string data?: any + /** Create a {@link RemoteRpcError}. */ constructor(error: RemoteRpcErrorParsed) { this.code = error.code this.message = error.message @@ -98,16 +100,19 @@ export class RemoteRpcError { } } +/** An error executing a request from the {@link LanguageServer}. */ export class LsRpcError { cause: RemoteRpcError | Error | string request: string params: object + /** Create an {@link LsRpcError}. */ constructor(cause: RemoteRpcError | Error | string, request: string, params: object) { this.cause = cause this.request = request this.params = params } + /** Get a human-readable string representation of this error. */ toString() { return `Language Server request '${this.request}' failed: ${this.cause instanceof RemoteRpcError ? this.cause.message : this.cause}` } @@ -140,6 +145,7 @@ export class LanguageServer extends ObservableV2 { return this.initialized.then(result => (result.ok ? result.value.contentRoots : [])) } + /** Reconnect the underlying network transport. */ reconnect() { this.transport.reconnect() } @@ -259,6 +268,7 @@ export class LanguageServer extends ObservableV2> { return this.acquireCapability('executionContext/canModify', { contextId }) } @@ -287,7 +297,7 @@ export class LanguageServer extends ObservableV2> { return this.request('text/applyEdit', { edit, execute, idMap }) } @@ -512,15 +522,19 @@ export class LanguageServer extends ObservableV2> { return this.request('ai/completion', { prompt, stopSequence }) } - /** A helper function to subscribe to file updates. + /** + * A helper function to subscribe to file updates. * Please use `ls.on('file/event')` directly if the initial `'Added'` notifications are not - * needed. */ + * needed. + */ watchFiles(rootId: Uuid, segments: string[], callback: (event: Event<'file/event'>) => void) { let running = true + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this return { promise: (async () => { @@ -550,6 +564,10 @@ export class LanguageServer extends ObservableV2 0) { this.retainCount -= 1 @@ -581,6 +604,7 @@ export class LanguageServer extends ObservableV2 { contents: T } -export interface TextFileContents extends FileContents {} +export type TextFileContents = FileContents export interface DirectoryTree { path: Path @@ -83,7 +83,7 @@ export type IdMapTuple = [IdMapSpan, string] export type IdMapTriple = [number, number, string] -export type RegisterOptions = { path: Path } | { contextId: ContextId } | {} +export type RegisterOptions = { path: Path } | { contextId: ContextId } | object export interface CapabilityRegistration { method: string @@ -102,46 +102,30 @@ export interface MethodCall { export type ExpressionUpdatePayload = Value | DataflowError | Panic | Pending -/** - * Indicates that the expression was computed to a value. - */ +/** Indicates that the expression was computed to a value. */ export interface Value { type: 'Value' - /** - * Information about attached warnings. - */ + /** Information about attached warnings. */ warnings?: Warnings - /** - * The schema of returned function value. - */ + /** The schema of returned function value. */ functionSchema?: FunctionSchema } -/** - * Indicates that the expression was computed to an error. - */ +/** Indicates that the expression was computed to an error. */ export interface DataflowError { type: 'DataflowError' - /** - * The list of expressions leading to the root error. - */ + /** The list of expressions leading to the root error. */ trace: ExpressionId[] } -/** - * Indicates that the expression failed with the runtime exception. - */ +/** Indicates that the expression failed with the runtime exception. */ export interface Panic { type: 'Panic' - /** - * The error message. - */ + /** The error message. */ message: string - /** - * The stack trace. - */ + /** The stack trace. */ trace: ExpressionId[] } @@ -157,15 +141,15 @@ export interface Pending { progress?: number } -/** - * Information about warnings associated with the value. - */ +/** Information about warnings associated with the value. */ export interface Warnings { /** The number of attached warnings. */ count: number - /** If the value has a single warning attached, this field contains textual + /** + * If the value has a single warning attached, this field contains textual * representation of the attached warning. In general, warning values should - * be obtained by attaching an appropriate visualization to a value. */ + * be obtained by attaching an appropriate visualization to a value. + */ value?: string } @@ -189,6 +173,7 @@ export interface MethodPointer { name: string } +/** Whether one {@link MethodPointer} deeply equals another. */ export function methodPointerEquals(left: MethodPointer, right: MethodPointer): boolean { return ( left.module === right.module && @@ -251,8 +236,10 @@ export type FileSystemObject = name: string path: Path } - /** A directory which contents have been truncated, i.e. with its subtree not listed any further - * due to depth limit being reached. */ + /** + * A directory which contents have been truncated, i.e. with its subtree not listed any further + * due to depth limit being reached. + */ | { type: 'DirectoryTruncated' name: string @@ -278,8 +265,10 @@ export type FileSystemObject = target: Path } -/** A single component of a component group. - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarycomponent) */ +/** + * A single component of a component group. + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarycomponent) + */ export interface LibraryComponent { /** The component name. */ name: string @@ -287,8 +276,10 @@ export interface LibraryComponent { shortcut?: string } -/** The component group provided by a library. - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarycomponentgroup) */ +/** + * The component group provided by a library. + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarycomponentgroup) + */ export interface LibraryComponentGroup { /** * The fully qualified library name. A string consisting of a namespace and @@ -296,7 +287,8 @@ export interface LibraryComponentGroup { * i.e. `Standard.Base`. */ library: string - /** The group name without the library name prefix. + /** + * The group name without the library name prefix. * E.g. given the `Standard.Base.Group 1` group reference, * the `name` field contains `Group 1`. */ @@ -310,8 +302,10 @@ export interface LibraryComponentGroup { export interface VisualizationConfiguration { /** An execution context of the visualization. */ executionContextId: ContextId - /** A qualified name of the module to be used to evaluate the arguments for the visualization - * expression. */ + /** + * A qualified name of the module to be used to evaluate the arguments for the visualization + * expression. + */ visualizationModule: string /** An expression that creates a visualization. */ expression: string | MethodPointer @@ -350,8 +344,8 @@ export type Notifications = { currentVersion: number }) => void 'file/event': (param: { path: Path; kind: FileEventKind }) => void - 'file/rootAdded': (param: {}) => void - 'file/rootRemoved': (param: {}) => void + 'file/rootAdded': (param: object) => void + 'file/rootRemoved': (param: object) => void 'refactoring/projectRenamed': (param: { oldNormalizedName: string newNormalizedName: string @@ -377,12 +371,14 @@ export interface LocalCall { expressionId: ExpressionId } +/** Serialize a {@link MethodPointer}. */ export function encodeMethodPointer(enc: encoding.Encoder, ptr: MethodPointer) { encoding.writeVarString(enc, ptr.module) encoding.writeVarString(enc, ptr.name) encoding.writeVarString(enc, ptr.definedOnType) } +/** Whether one {@link StackItem} is deeply equal to another. */ export function stackItemsEqual(left: StackItem, right: StackItem): boolean { if (left.type !== right.type) return false @@ -406,7 +402,7 @@ export namespace response { contentRoots: ContentRoot[] } - export interface FileContents extends TextFileContents {} + export type FileContents = TextFileContents export interface FileExists { exists: boolean @@ -480,9 +476,11 @@ export interface LanguageServerError { export enum LanguageServerErrorCode { // === Error API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/ErrorApi.scala - /** The user doesn't have access to the requested resource. + /** + * The user doesn't have access to the requested resource. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#accessdeniederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#accessdeniederror) + */ AccessDenied = 100, // === VCS Manager API errors === @@ -501,208 +499,296 @@ export enum LanguageServerErrorCode { // === File Manager API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/filemanager/FileManagerApi.scala - /** A miscellaneous file system error. + /** + * A miscellaneous file system error. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filesystemerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filesystemerror) + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values FileSystem = 1000, - /** The requested content root could not be found. + /** + * The requested content root could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#contentrootnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#contentrootnotfounderror) + */ ContentRootNotFound = 1001, - /** The requested file does not exist. + /** + * The requested file does not exist. * - *[Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filenotfound) */ + *[Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filenotfound) + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values FileNotFound = 1003, - /** The file trying to be created already exists. + /** + * The file trying to be created already exists. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#fileexists) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#fileexists) + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values FileExists = 1004, - /** The IO operation timed out. + /** + * The IO operation timed out. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#operationtimeouterror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#operationtimeouterror) + */ + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values OperationTimeoutError = 1005, - /** The provided path is not a directory. + /** + * The provided path is not a directory. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#notdirectory) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#notdirectory) + */ NotDirectory = 1006, - /** The provided path is not a file. + /** + * The provided path is not a file. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#notfile) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#notfile) + */ NotFile = 1007, - /** The streaming file write cannot overwrite a portion of the requested file. + /** + * The streaming file write cannot overwrite a portion of the requested file. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#cannotoverwrite) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#cannotoverwrite) + */ CannotOverwrite = 1008, - /** The requested file read was out of bounds for the file's size. + /** + * The requested file read was out of bounds for the file's size. * * The actual length of the file is returned in `payload.fileLength`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#readoutofbounds) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#readoutofbounds) + */ ReadOutOfBounds = 1009, - /** The project configuration cannot be decoded. + /** + * The project configuration cannot be decoded. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#cannotdecode) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#cannotdecode) + */ CannotDecode = 1010, // === Execution API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala - /** The provided execution stack item could not be found. + /** + * The provided execution stack item could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#stackitemnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#stackitemnotfounderror) + */ StackItemNotFound = 2001, - /** The provided exeuction context could not be found. + /** + * The provided exeuction context could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#contextnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#contextnotfounderror) + */ ContextNotFound = 2002, - /** The execution stack is empty. + /** + * The execution stack is empty. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#emptystackerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#emptystackerror) + */ EmptyStack = 2003, - /** The stack is invalid in this context. + /** + * The stack is invalid in this context. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidstackitemerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidstackitemerror) + */ InvalidStackItem = 2004, - /** The provided module could not be found. + /** + * The provided module could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#modulenotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#modulenotfounderror) + */ ModuleNotFound = 2005, - /** The provided visualization could not be found. + /** + * The provided visualization could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#visualizationnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#visualizationnotfounderror) + */ VisualizationNotFound = 2006, - /** The expression specified in the {@link VisualizationConfiguration} cannot be evaluated. + /** + * The expression specified in the {@link VisualizationConfiguration} cannot be evaluated. * * If relevant, a {@link Diagnostic} containing error details is returned as `payload`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#visualizationexpressionerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#visualizationexpressionerror) + */ VisualizationExpression = 2007, // === Text API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/text/TextApi.scala - /** A file was not opened. + /** + * A file was not opened. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filenotopenederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filenotopenederror) + */ FileNotOpened = 3001, - /** Validation has failed for a series of text edits. + /** + * Validation has failed for a series of text edits. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#texteditvalidationerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#texteditvalidationerror) + */ TextEditValidation = 3002, - /** The version provided by a client does not match the version computed by the server. + /** + * The version provided by a client does not match the version computed by the server. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidversionerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidversionerror) + */ InvalidVersion = 3003, - /** The client doesn't hold write lock to the buffer. + /** + * The client doesn't hold write lock to the buffer. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#writedeniederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#writedeniederror) + */ WriteDenied = 3004, // === Capability API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/capability/CapabilityApi.scala - /** The requested capability is not acquired. + /** + * The requested capability is not acquired. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#accessdeniederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#accessdeniederror) + */ CapabilityNotAcquired = 5001, // === Session API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/session/SessionApi.scala - /** The request could not be proccessed, beacuse the session is not initialised. + /** + * The request could not be proccessed, beacuse the session is not initialised. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#sessionnotinitialisederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#sessionnotinitialisederror) + */ SessionNotInitialised = 6001, - /** The session is already initialised. + /** + * The session is already initialised. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#sessionalreadyinitialisederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#sessionalreadyinitialisederror) + */ SessionAlreadyInitialised = 6002, // === Search API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/search/SearchApi.scala - /** There was an unexpected error accessing the suggestions database. + /** + * There was an unexpected error accessing the suggestions database. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#suggestionsdatabaseerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#suggestionsdatabaseerror) + */ SuggestionsDatabase = 7001, - /** The project was not found in the root directory. + /** + * The project was not found in the root directory. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#projectnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#projectnotfounderror) + */ ProjectNotFound = 7002, - /** The module name could not be resolved for the given file. + /** + * The module name could not be resolved for the given file. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#modulenamenotresolvederror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#modulenamenotresolvederror) + */ ModuleNameNotResolved = 7003, - /** The requested suggestion could not be found. + /** + * The requested suggestion could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#suggestionnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#suggestionnotfounderror) + */ SuggestionNotFound = 7004, // === Library API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala - /** The requested edition could not be found. + /** + * The requested edition could not be found. * * The requested edition is returned in `payload.editionName`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#editionnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#editionnotfounderror) + */ EditionNotFound = 8001, - /** A local library with the specified namespace and name combination already exists, so it cannot be created again. + /** + * A local library with the specified namespace and name combination already exists, so it cannot be created again. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#libraryalreadyexists) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#libraryalreadyexists) + */ LibraryAlreadyExists = 8002, - /** Authentication to the library repository was declined. + /** + * Authentication to the library repository was declined. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#libraryrepositoryauthenticationerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#libraryrepositoryauthenticationerror) + */ LibraryRepositoryAuthentication = 8003, - /** A request to the library repository failed. + /** + * A request to the library repository failed. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarypublisherror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarypublisherror) + */ LibraryPublish = 8004, - /** Uploading the library failed for network-related reasons. + /** + * Uploading the library failed for network-related reasons. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#libraryuploaderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#libraryuploaderror) + */ LibraryUpload = 8005, - /** Downloading the library failed for network-related reasons, or the library was not found in the repository. + /** + * Downloading the library failed for network-related reasons, or the library was not found in the repository. * * The requested library is returned in `payload.namespace`, `payload.name`, and `payload.version`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarydownloaderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarydownloaderror) + */ LibraryDownload = 8006, - /** A local library with the specified namespace and name combination was not found on the local libraries path. + /** + * A local library with the specified namespace and name combination was not found on the local libraries path. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#locallibrarynotfound) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#locallibrarynotfound) + */ LocalLibraryNotFound = 8007, - /** A library could not be resolved. It was not defined in the edition, and the settings did not + /** + * A library could not be resolved. It was not defined in the edition, and the settings did not * allow to resolve local libraries, or it did not exist there either. * * The requested namespace and name are returned in `payload.namespace` and `payload.name`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarynotresolved) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#librarynotresolved) + */ LibraryNotResolved = 8008, - /** The chosen library name is invalid. + /** + * The chosen library name is invalid. * * A similar, valid name is returned in `payload.suggestedName`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidlibraryname) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidlibraryname) + */ InvalidLibraryName = 8009, - /** The library preinstall endpoint could not properly find dependencies of the requested library. + /** + * The library preinstall endpoint could not properly find dependencies of the requested library. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#dependencydiscoveryerror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#dependencydiscoveryerror) + */ DependencyDiscovery = 8010, - /** The provided version string is not a valid semver version. + /** + * The provided version string is not a valid semver version. * * The requested version is returned in `payload.version`. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidsemverversion) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#invalidsemverversion) + */ InvalidSemverVersion = 8011, // === Refactoring API errors === // https://github.com/enso-org/enso/blob/develop/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala - /** An expression with the provided ID could not be found. + /** + * An expression with the provided ID could not be found. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#expressionnotfounderror) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#expressionnotfounderror) + */ ExpressionNotFound = 9001, - /** The refactoring operation was not able to apply the generated edits. + /** + * The refactoring operation was not able to apply the generated edits. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#failedtoapplyedits) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#failedtoapplyedits) + */ FailedToApplyEdits = 9002, - /** Refactoring of the given expression is not supported. + /** + * Refactoring of the given expression is not supported. * - * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#refactoringnotsupported) */ + * [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#refactoringnotsupported) + */ RefactoringNotSupported = 9003, } diff --git a/app/ydoc-shared/src/languageServerTypes/suggestions.ts b/app/ydoc-shared/src/languageServerTypes/suggestions.ts index 217cb60c550..3ee965f8db3 100644 --- a/app/ydoc-shared/src/languageServerTypes/suggestions.ts +++ b/app/ydoc-shared/src/languageServerTypes/suggestions.ts @@ -19,9 +19,7 @@ export interface SuggestionEntryArgument { } export interface Position { - /** - * Line position in a document (zero-based). - */ + /** Line position in a document (zero-based). */ line: number /** @@ -202,28 +200,20 @@ namespace suggestionEntryVariant { } export interface SuggestionsDatabaseEntry { - /** - * The suggestion entry id. - */ + /** The suggestion entry id. */ id: SuggestionId - /** - * The suggestion entry. - */ + /** The suggestion entry. */ suggestion: SuggestionEntry } type FieldAction = 'Remove' | 'Set' export interface FieldUpdate { - /** - * The modifying action. - */ + /** The modifying action. */ tag: FieldAction - /** - * The updated value. - */ + /** The updated value. */ value?: T } @@ -235,55 +225,37 @@ export type SuggestionArgumentUpdate = namespace suggestionArgumentUpdateVariant { export interface Add { type: 'Add' - /** - * The position of the argument. - */ + /** The position of the argument. */ index: number - /** - * The argument to add. - */ + /** The argument to add. */ argument: SuggestionEntryArgument } export interface Remove { type: 'Remove' - /** - * The position of the argument. - */ + /** The position of the argument. */ index: number } export interface Modify { type: 'Modify' - /** - * The position of the argument. - */ + /** The position of the argument. */ index: number - /** - * The name to update. - */ + /** The name to update. */ name?: FieldUpdate - /** - * The argument type to update. - */ + /** The argument type to update. */ reprType?: FieldUpdate - /** - * The isSuspended flag to update. - */ + /** The isSuspended flag to update. */ isSuspended?: FieldUpdate - /** - * The hasDefault flag to update. - */ + /** The hasDefault flag to update. */ hasDefault?: FieldUpdate - /** - * The default value to update. - */ + /** The default value to update. */ defaultValue?: FieldUpdate } } @@ -296,70 +268,46 @@ export type SuggestionsDatabaseUpdate = namespace suggestionDatabaseUpdateVariant { export interface Add { type: 'Add' - /** - * Suggestion entry id. - */ + /** Suggestion entry id. */ id: SuggestionId - /** - * Suggestion entry. - */ + /** Suggestion entry. */ suggestion: SuggestionEntry } export interface Remove { type: 'Remove' - /** - * Suggestion entry id. - */ + /** Suggestion entry id. */ id: SuggestionId } export interface Modify { type: 'Modify' - /** - * Suggestion entry id. - */ + /** Suggestion entry id. */ id: SuggestionId - /** - * The external id to update. - */ + /** The external id to update. */ externalId?: FieldUpdate - /** - * The list of argument updates. - */ + /** The list of argument updates. */ arguments?: SuggestionArgumentUpdate[] - /** - * The module name to update. - */ + /** The module name to update. */ module?: FieldUpdate - /** - * The self type to update. - */ + /** The self type to update. */ selfType?: FieldUpdate - /** - * The return type to update. - */ + /** The return type to update. */ returnType?: FieldUpdate - /** - * The documentation string to update. - */ + /** The documentation string to update. */ documentation?: FieldUpdate - /** - * The scope to update. - */ + /** The scope to update. */ scope?: FieldUpdate - /** - * The reexport field to update. - */ + /** The reexport field to update. */ reexport?: FieldUpdate } } diff --git a/app/ydoc-shared/src/util/assert.ts b/app/ydoc-shared/src/util/assert.ts index 81882739fb2..0d53cd35a5b 100644 --- a/app/ydoc-shared/src/util/assert.ts +++ b/app/ydoc-shared/src/util/assert.ts @@ -1,7 +1,17 @@ +/** + * Assert that the current branch should be unreachable. + * This function should never be called at runtime due to its parameter being of type `never`. + * Being a type with zero values, it is impossible to construct an instance of this type at + * runtime. + */ export function assertNever(x: never): never { bail('Unexpected object: ' + JSON.stringify(x)) } +/** + * A type assertion that a condition is `true`. + * Throw an error if the condtion is `false`. + */ export function assert(condition: boolean, message?: string): asserts condition { if (!condition) bail(message ? `Assertion failed: ${message}` : 'Assertion failed') } @@ -13,7 +23,6 @@ export function assert(condition: boolean, message?: string): asserts condition * @param iterable The iterable to check. * @param length The expected length of the iterable. * @param message Optional message for the assertion error. - * @return void * @throws Error Will throw an error if the length does not match. * * The first five elements of the iterable will be displayed in the error message @@ -33,25 +42,30 @@ export function assertLength(iterable: Iterable, length: number, message?: ) } +/** Assert that an iterable contains zero elements. */ export function assertEmpty(iterable: Iterable, message?: string): void { assertLength(iterable, 0, message) } +/** Assert that two values are equal (by reference for reference types, by value for value types). */ export function assertEqual(actual: T, expected: T, message?: string) { const messagePrefix = message ? message + ' ' : '' assert(actual === expected, `${messagePrefix}Expected ${expected}, got ${actual}.`) } +/** Assert that two values are not equal (by reference for reference types, by value for value types). */ export function assertNotEqual(actual: T, unexpected: T, message?: string) { const messagePrefix = message ? message + ' ' : '' assert(actual !== unexpected, `${messagePrefix}Expected not ${unexpected}, got ${actual}.`) } +/** A type assertion that a given value is not `undefined`. */ export function assertDefined(x: T | undefined, message?: string): asserts x is T { const messagePrefix = message ? message + ' ' : '' assert(x !== undefined, `${messagePrefix}Expected value to be defined.`) } +/** Assert that this case is unreachable. */ export function assertUnreachable(): never { bail('Unreachable code') } diff --git a/app/ydoc-shared/src/util/data/__tests__/text.test.ts b/app/ydoc-shared/src/util/data/__tests__/text.test.ts index a8dfbdd3e87..63c02dacd08 100644 --- a/app/ydoc-shared/src/util/data/__tests__/text.test.ts +++ b/app/ydoc-shared/src/util/data/__tests__/text.test.ts @@ -15,7 +15,8 @@ test.prop({ expect(applyTextEdits(beforeString, edits)).toBe(afterString) }) -/** Test that `textChangeToEdits` and `applyTextEdits` work when inputs contain any special characters representable by +/** + * Test that `textChangeToEdits` and `applyTextEdits` work when inputs contain any special characters representable by * a `string`, including newlines and even incomplete surrogate pairs (invalid Unicode). */ test.prop({ @@ -29,7 +30,8 @@ test.prop({ expect(applyTextEdits(beforeString, edits)).toBe(afterString) }) -/** Tests that: +/** + * Tests that: * - When the code in `a[0]` is edited to become the code in `b[0]`, * `applyTextEditsToSpans` followed by `trimEnd` transforms the spans in `a.slice(1)` into the spans in `b.slice(1)`. * - The same holds when editing from `b` to `a`. @@ -39,7 +41,8 @@ function checkCorrespondence(a: string[], b: string[]) { checkCorrespondenceForward(b, a) } -/** Performs the same check as {@link checkCorrespondence}, for correspondences that are not expected to be reversible. +/** + Performs the same check as {@link checkCorrespondence}, for correspondences that are not expected to be reversible. */ function checkCorrespondenceForward(before: string[], after: string[]) { const leadingSpacesAndLength = (input: string): [number, number] => [ diff --git a/app/ydoc-shared/src/util/data/iterable.ts b/app/ydoc-shared/src/util/data/iterable.ts index 1e338408ace..eb1a911d81a 100644 --- a/app/ydoc-shared/src/util/data/iterable.ts +++ b/app/ydoc-shared/src/util/data/iterable.ts @@ -1,7 +1,12 @@ /** @file Functions for manipulating {@link Iterable}s. */ +/** An iterable with zero elements. */ export function* empty(): Generator {} +/** + * An iterable `yield`ing numeric values with the given step between the start (inclusive) + * and the end (exclusive). + */ export function* range(start: number, stop: number, step = start <= stop ? 1 : -1) { if ((step > 0 && start > stop) || (step < 0 && start < stop)) { throw new Error( @@ -21,22 +26,38 @@ export function* range(start: number, stop: number, step = start <= stop ? 1 : - } } +/** + * Return an {@link Iterable} that `yield`s values that are the result of calling the given + * function on the next value of the given source iterable. + */ export function* map(iter: Iterable, map: (value: T) => U): IterableIterator { for (const value of iter) { yield map(value) } } +/** + * Return an {@link Iterable} that `yield`s only the values from the given source iterable + * that pass the given predicate. + */ export function* filter(iter: Iterable, include: (value: T) => boolean): IterableIterator { for (const value of iter) if (include(value)) yield value } +/** + * Return an {@link Iterable} that `yield`s values from each iterable sequentially, + * yielding values from the second iterable only after exhausting the first iterable, and so on. + */ export function* chain(...iters: Iterable[]) { for (const iter of iters) { yield* iter } } +/** + * Return an iterable that `yield`s the next value from both given source iterables at the same time. + * Stops `yield`ing when *either* iterable is exhausted. + */ export function* zip(left: Iterable, right: Iterable): Generator<[T, U]> { const leftIterator = left[Symbol.iterator]() const rightIterator = right[Symbol.iterator]() @@ -48,6 +69,10 @@ export function* zip(left: Iterable, right: Iterable): Generator<[T, } } +/** + * Return an iterable that `yield`s the next value from both given source iterables at the same time. + * `yield`s `undefined` for the shorter iterator when it is exhausted. + */ export function* zipLongest( left: Iterable, right: Iterable, @@ -65,6 +90,10 @@ export function* zipLongest( } } +/** + * Return the value of the iterator if and only if it contains exactly one value. + * Otherwise, return `undefined`. + */ export function tryGetSoleValue(iter: Iterable): T | undefined { const iterator = iter[Symbol.iterator]() const result = iterator.next() @@ -74,25 +103,29 @@ export function tryGetSoleValue(iter: Iterable): T | undefined { return result.value } -/** Utility to simplify consuming an iterator a part at a time. */ +/** Utility to simplify consuming an iterator one part at a time. */ export class Resumable { private readonly iterator: Iterator private current: IteratorResult + /** Create a {@link Resumable}. */ constructor(iterable: Iterable) { this.iterator = iterable[Symbol.iterator]() this.current = this.iterator.next() } + /** The current value of the iterator. */ peek() { return this.current.done ? undefined : this.current.value } + /** Advance the iterator, saving the new current value of the iterator. */ advance() { this.current = this.iterator.next() } - /** The given function peeks at the current value. If the function returns `true`, the current value will be advanced - * and the function called again; if it returns `false`, the peeked value remains current and `advanceWhile` returns. + /** + * The given function peeks at the current value. If the function returns `true`, the current value will be advanced + * and the function called again; if it returns `false`, the peeked value remains current and `advanceWhile` returns. */ advanceWhile(f: (value: T) => boolean) { while (!this.current.done && f(this.current.value)) { diff --git a/app/ydoc-shared/src/util/data/opt.ts b/app/ydoc-shared/src/util/data/opt.ts index 968c03b25b1..e91ef96eef7 100644 --- a/app/ydoc-shared/src/util/data/opt.ts +++ b/app/ydoc-shared/src/util/data/opt.ts @@ -1,6 +1,7 @@ /** @file A value that may be `null` or `undefined`. */ -/** Optional value type. This is a replacement for `T | null | undefined` that is more +/** + * Optional value type. This is a replacement for `T | null | undefined` that is more * convenient to use. We do not select a single value to represent "no value", because we are using * libraries that disagree whether `null` (e.g. Yjs) or `undefined` (e.g. Vue) should be used for * that purpose. We want to be compatible with both without needless conversions. In our own code, @@ -9,17 +10,24 @@ * `isSome` function. * * Note: For JSON-serialized data, prefer explicit `null` over `undefined`, since `undefined` is - * not serializable. Alternatively, use optional field syntax (e.g. `{ x?: number }`). */ + * not serializable. Alternatively, use optional field syntax (e.g. `{ x?: number }`). + */ export type Opt = T | null | undefined +/** Whether the given {@link Opt} is non-nullish. */ export function isSome(value: Opt): value is T { return value != null } +/** Whether the given {@link Opt} is nullish. */ export function isNone(value: Opt): value is null | undefined { return value == null } +/** + * Map the value inside the given {@link Opt} if it is not nullish, + * else return the given fallback value. + */ export function mapOr(optional: Opt, fallback: R, mapper: (value: T) => R): R { return isSome(optional) ? mapper(optional) : fallback } diff --git a/app/ydoc-shared/src/util/data/result.ts b/app/ydoc-shared/src/util/data/result.ts index 384bddb65f0..2489ad27bee 100644 --- a/app/ydoc-shared/src/util/data/result.ts +++ b/app/ydoc-shared/src/util/data/result.ts @@ -1,5 +1,7 @@ -/** @file A generic type that can either hold a value representing a successful result, - * or an error. */ +/** + * @file A generic type that can either hold a value representing a successful result, + * or an error. + */ import { isSome, type Opt } from './opt' @@ -26,49 +28,38 @@ export type Result = | { ok: true; value: T } | { ok: false; error: ResultError } -/** - * Constructor of success {@link Result}. - */ +/** Constructor of success {@link Result}. */ export function Ok(): Result export function Ok(data: T): Result +/** Implementation of `Ok` constructor. */ export function Ok(data?: T): Result { return { ok: true, value: data } } -/** - * Constructor of error {@link Result}. - */ +/** Constructor of error {@link Result}. */ export function Err(error: E): Result { return { ok: false, error: new ResultError(error) } } -/** - * Helper function for converting optional value to {@link Result}. - */ +/** Helper function for converting optional value to {@link Result}. */ export function okOr(data: Opt, error: E): Result { if (isSome(data)) return Ok(data) else return Err(error) } -/** - * Unwraps the {@link Result} value. If the result is error, it is thrown. - */ +/** Unwraps the {@link Result} value. If the result is error, it is thrown. */ export function unwrap(result: Result): T { if (result.ok) return result.value else throw result.error } -/** - * Unwraps the {@link Result} value. If the result is error, an alternative is returned. - */ +/** Unwraps the {@link Result} value. If the result is error, an alternative is returned. */ export function unwrapOr(result: Result, alternative: A): T | A { if (result.ok) return result.value else return alternative } -/** - * Unwraps the {@link Result} value. If the result is error, it is logged and alternative is returned. - */ +/** Unwraps the {@link Result} value. If the result is error, it is logged and alternative is returned. */ export function unwrapOrWithLog( result: Result, alternative: A, @@ -81,22 +72,17 @@ export function unwrapOrWithLog( } } -/** - * Maps the {@link Result} value. - */ +/** Maps the {@link Result} value. */ export function mapOk(result: Result, f: (value: T) => U): Result { if (result.ok) return Ok(f(result.value)) else return result } -/** - * If the value is nullish, returns {@link Ok} with it. - */ +/** If the value is nullish, returns {@link Ok} with it. */ export function transposeResult(value: Opt>): Result, E> -/** - * If any of the values is an error, the first error is returned. - */ +/** If any of the values is an error, the first error is returned. */ export function transposeResult(value: Result[]): Result +/** Implementation of `transposeResult`. */ export function transposeResult(value: Opt> | Result[]) { if (value == null) return Ok(value) if (value instanceof Array) { @@ -107,9 +93,7 @@ export function transposeResult(value: Opt> | Result[]) return value } -/** - * Check if given value is {@link Result}. - */ +/** Check if given value is {@link Result}. */ export function isResult(v: unknown): v is Result { return ( v != null && @@ -120,22 +104,19 @@ export function isResult(v: unknown): v is Result { ) } -/** - * A class containing information about {@link Result} error. - */ +/** A class containing information about {@link Result} error. */ export class ResultError { payload: E /** All contexts attached by {@link withContext} function */ context: (() => string)[] + /** Create an {@link ResultError}. */ constructor(payload: E) { this.payload = payload this.context = [] } - /** - * Log the error with context information and given preable. - */ + /** Log the error with context information and given preable. */ log(preamble: string = 'Error') { console.error(this.message(preamble)) } @@ -193,6 +174,7 @@ export function withContext( context: () => string, f: () => Promise>, ): Promise> +/** Implementation of `withContext`. */ export function withContext( context: () => string, f: () => Promise> | Result, @@ -212,9 +194,7 @@ export function withContext( } } -/** - * Catch promise rejection of provided types and convert them to a Result type. - */ +/** Catch promise rejection of provided types and convert them to a Result type. */ export function rejectionToResult any>( errorKinds: ErrorKind | ErrorKind[], ): (promise: Promise) => Promise>> { diff --git a/app/ydoc-shared/src/util/data/text.ts b/app/ydoc-shared/src/util/data/text.ts index fdd680791c7..5615b02c74d 100644 --- a/app/ydoc-shared/src/util/data/text.ts +++ b/app/ydoc-shared/src/util/data/text.ts @@ -60,11 +60,13 @@ export function offsetEdit(textEdit: SourceRangeEdit, offset: number): SourceRan return { ...textEdit, range: [textEdit.range[0] + offset, textEdit.range[1] + offset] } } -/** Given: +/** + * Given: * @param textEdits - A change described by a set of text edits. * @param spansBefore - A collection of spans in the text before the edit. * @returns - A sequence of: Each span from `spansBefore` paired with the smallest span of the text after the edit that - * contains all text that was in the original span and has not been deleted. */ + * contains all text that was in the original span and has not been deleted. + */ export function applyTextEditsToSpans(textEdits: SourceRangeEdit[], spansBefore: SourceRange[]) { // Gather start and end points. const numerically = (a: number, b: number) => a - b @@ -117,7 +119,8 @@ export interface SpanTree { children(): IterableIterator> } -/** Given a span tree and some ranges, for each range find the smallest node that fully encloses it. +/** + * Given a span tree and some ranges, for each range find the smallest node that fully encloses it. * Return nodes paired with the ranges that are most closely enclosed by them. */ export function enclosingSpans( diff --git a/app/ydoc-shared/src/util/net.ts b/app/ydoc-shared/src/util/net.ts index 63292309d21..ae7abf8873d 100644 --- a/app/ydoc-shared/src/util/net.ts +++ b/app/ydoc-shared/src/util/net.ts @@ -17,16 +17,20 @@ interface Disposable { dispose(): void } +/** A scope which controls */ export class AbortScope { private ctrl: AbortController = new AbortController() + /** Get the {@link AbortSignal} for this {@link AbortScope}. */ get signal() { return this.ctrl.signal } + /** Trigger an abort for all listeners of this {@link AbortScope}. */ dispose(reason?: string) { this.ctrl.abort(reason) } + /** Trigger disposal of the given {@link Disposable} when this {@link AbortScope} is aborted. */ handleDispose(disposable: Disposable) { this.signal.throwIfAborted() this.onAbort(disposable.dispose.bind(disposable)) @@ -42,6 +46,7 @@ export class AbortScope { return child } + /** Call the given callback when this {@link AbortScope} is aborted. */ onAbort(listener: () => void) { if (this.signal.aborted) { queueMicrotask(listener) @@ -50,6 +55,10 @@ export class AbortScope { } } + /** + * Add the given event listener on the given event on the given observable, + * removing the event listener when this {@link AbortScope} is aborted. + */ handleObserve< EVENTS extends { [key in keyof EVENTS]: (...arg0: any[]) => void }, NAME extends keyof EVENTS & string, @@ -66,10 +75,12 @@ export interface BackoffOptions { retryDelay?: number retryDelayMultiplier?: number retryDelayMax?: number - /** Called when the promise throws an error, and the next retry is about to be attempted. + /** + * Called when the promise throws an error, and the next retry is about to be attempted. * When this function returns `false`, the backoff is immediately aborted. When this function * is not provided, the backoff will always continue until the maximum number of retries - * is reached. * */ + * is reached. * + */ onBeforeRetry?: ( error: ResultError, retryCount: number, @@ -78,9 +89,11 @@ export interface BackoffOptions { ) => boolean | void /** Called right before returning. */ onSuccess?: (retryCount: number) => void - /** Called after the final retry, right before throwing an error. + /** + * Called after the final retry, right before throwing an error. * Note that `onBeforeRetry` is *not* called on the final retry, as there is nothing after the - * final retry. */ + * final retry. + */ onFailure?: (error: ResultError, retryCount: number) => void } @@ -132,6 +145,7 @@ export async function exponentialBackoff( } } +/** An `onBeforeRetry` handler used in {@link printingCallbacks} that logs an error. */ export function defaultOnBeforeRetry( description: string, ): NonNullable['onBeforeRetry']> { @@ -145,6 +159,7 @@ export function defaultOnBeforeRetry( } } +/** An `onFailure` handler used in {@link printingCallbacks} that logs an error. */ export function defaultOnFailure( description: string, ): NonNullable['onFailure']> { @@ -156,6 +171,7 @@ export function defaultOnFailure( } } +/** An `onSuccess` handler used in {@link printingCallbacks} that logs a message. */ export function defaultOnSuccess( description: string, ): NonNullable['onSuccess']> { @@ -169,8 +185,10 @@ export function defaultOnSuccess( } } -/** @param successDescription Should be in past tense, without an initial capital letter. - * @param errorDescription Should be in present tense, without an initial capital letter. */ +/** + * @param successDescription Should be in past tense, without an initial capital letter. + * @param errorDescription Should be in present tense, without an initial capital letter. + */ export function printingCallbacks(successDescription: string, errorDescription: string) { return { onBeforeRetry: defaultOnBeforeRetry(errorDescription), diff --git a/app/ydoc-shared/src/util/net/MockWSTransport.ts b/app/ydoc-shared/src/util/net/MockWSTransport.ts index 3b94057c3bc..5c65c2c9e8a 100644 --- a/app/ydoc-shared/src/util/net/MockWSTransport.ts +++ b/app/ydoc-shared/src/util/net/MockWSTransport.ts @@ -12,22 +12,39 @@ export interface MockTransportData { (method: Methods, params: any, transport: MockWebSocketTransport): Promise } +/** A mock WebSocket transport, only for use in tests. */ export class MockWebSocketTransport extends ReconnectingWebSocketTransport { static mocks: Map = new Map() private openEventListeners = new Set<(event: WebSocketEventMap['open']) => void>() + /** Create an {@link MockWebSocketTransport}. */ constructor(public name: string) { super('') } + /** Add a handler for the {@link MockWebSocketTransport} with the given name. */ static addMock(name: string, data: MockTransportData) { MockWebSocketTransport.mocks.set(name, data as any) } + /** Simulate connecting to a WebSocket. */ override connect(): Promise { for (const listener of this.openEventListeners) listener(new Event('open')) return Promise.resolve() } + /** + * Simulate reconnecting to a WebSocket. + * Currently unimplemented as the functionality is not needed for tests. + */ override reconnect() {} + /** + * Simulate closing a WebSocket. + * Currently unimplemented as the functionality is not needed for tests. + */ override close(): void {} + /** + * Respond to the given JSON-RPC request, calling the mock implementation + * registered with {@link MockWebSocketTransport['addMock']}. + * Returns a rejected {@link Promise} if there is no corresponding mock implementation. + */ override sendData(data: JSONRPCRequestData, timeout?: number | null): Promise { if (Array.isArray(data)) return Promise.all(data.map(d => this.sendData(d.request, timeout))) return ( @@ -38,6 +55,7 @@ export class MockWebSocketTransport extends ReconnectingWebSocketTransport { ) ?? Promise.reject() ) } + /** Emit a JSON-RPC notification. */ emit(method: N, params: ArgumentsType[0]): void { this.transportRequestManager.transportEventChannel.emit('notification', { jsonrpc: '2.0', @@ -46,22 +64,22 @@ export class MockWebSocketTransport extends ReconnectingWebSocketTransport { } as IJSONRPCNotificationResponse) } + /** Add an event listener for the given event. */ override on( type: K, cb: ( event: WebSocketEventMap[K] extends Event ? WebSocketEventMap[K] : never, ) => WebSocketEventMap[K] extends Event ? void : never, - options?: AddEventListenerOptions, ): void { if (type === 'open') this.openEventListeners.add(cb as any) } + /** Remove an event listener for the given event. */ override off( type: K, cb: ( event: WebSocketEventMap[K] extends Event ? WebSocketEventMap[K] : never, ) => WebSocketEventMap[K] extends Event ? void : never, - options?: AddEventListenerOptions, ): void { if (type === 'open') this.openEventListeners.delete(cb as any) } diff --git a/app/ydoc-shared/src/util/net/ReconnectingWSTransport.ts b/app/ydoc-shared/src/util/net/ReconnectingWSTransport.ts index 753530a31e2..4b681c0736e 100644 --- a/app/ydoc-shared/src/util/net/ReconnectingWSTransport.ts +++ b/app/ydoc-shared/src/util/net/ReconnectingWSTransport.ts @@ -18,8 +18,10 @@ export interface AddEventListenerOptions { signal?: AbortSignal } +/** A socket that automatically connects upon disconnect, for example after network issues. */ export class ReconnectingWebSocketTransport extends WebSocketTransport { private _reconnectingConnection: ReconnectingWebSocket + /** Create a {@link ReconnectingWebSocketTransport}. */ constructor(uri: string, wsOptions: Options = {}) { super(uri) this.uri = uri @@ -31,10 +33,12 @@ export class ReconnectingWebSocketTransport extends WebSocketTransport { this.connection = this._reconnectingConnection as any } + /** Reconnect the underlying WebSocket. */ public reconnect() { this._reconnectingConnection.reconnect() } + /** Add an event listener to the underlying WebSocket. */ on( type: K, cb: ( @@ -45,6 +49,7 @@ export class ReconnectingWebSocketTransport extends WebSocketTransport { this._reconnectingConnection.addEventListener(type, cb, options) } + /** Remove an event listener from the underlying WebSocket. */ off( type: K, cb: ( diff --git a/app/ydoc-shared/src/util/types.ts b/app/ydoc-shared/src/util/types.ts index 8a443e38d59..4f62800d8df 100644 --- a/app/ydoc-shared/src/util/types.ts +++ b/app/ydoc-shared/src/util/types.ts @@ -1,5 +1,7 @@ -/** Returns an all the keys of a type. The argument provided is required to be an object containing all the keys of the - * type (including optional fields), but the associated values are ignored and may be of any type. */ +/** + * Returns an all the keys of a type. The argument provided is required to be an object containing all the keys of the + * type (including optional fields), but the associated values are ignored and may be of any type. + */ export function allKeys(keys: { [P in keyof T]-?: any }): ReadonlySet { return Object.freeze(new Set(Object.keys(keys))) } diff --git a/app/ydoc-shared/src/uuid.ts b/app/ydoc-shared/src/uuid.ts index 998ddc5ce92..2d588be00cc 100644 --- a/app/ydoc-shared/src/uuid.ts +++ b/app/ydoc-shared/src/uuid.ts @@ -1,11 +1,13 @@ import type { Uuid } from './yjsModel' +/** Return the textual representation of a UUID from its 64 high and 64 low bits. */ export function uuidFromBits(leastSigBits: bigint, mostSigBits: bigint): Uuid { const bits = (mostSigBits << 64n) | leastSigBits const string = bits.toString(16).padStart(32, '0') return string.replace(/(........)(....)(....)(....)(............)/, '$1-$2-$3-$4-$5') as Uuid } +/** Return the 64 high and 64 low bits of a UUID from its textual representation. */ export function uuidToBits(uuid: string): [leastSigBits: bigint, mostSigBits: bigint] { const bits = BigInt('0x' + uuid.replace(/-/g, '')) return [bits & 0xffffffffffffffffn, bits >> 64n] diff --git a/app/ydoc-shared/src/yjsModel.ts b/app/ydoc-shared/src/yjsModel.ts index aae5e4efb52..0a55f452fdf 100644 --- a/app/ydoc-shared/src/yjsModel.ts +++ b/app/ydoc-shared/src/yjsModel.ts @@ -25,6 +25,7 @@ export interface VisualizationMetadata { height: number | null } +/** TODO: Add docs */ export function visMetadataEquals( a: VisualizationMetadata | null | undefined, b: VisualizationMetadata | null | undefined, @@ -40,6 +41,7 @@ export function visMetadataEquals( ) } +/** TODO: Add docs */ export function visIdentifierEquals( a: VisualizationIdentifier | null | undefined, b: VisualizationIdentifier | null | undefined, @@ -49,12 +51,14 @@ export function visIdentifierEquals( export type ProjectSetting = string +/** TODO: Add docs */ export class DistributedProject { doc: Y.Doc name: Y.Text modules: Y.Map settings: Y.Map + /** TODO: Add docs */ constructor(doc: Y.Doc) { this.doc = doc this.name = this.doc.getText('name') @@ -62,10 +66,12 @@ export class DistributedProject { this.settings = this.doc.getMap('settings') } + /** TODO: Add docs */ moduleNames(): string[] { return Array.from(this.modules.keys()) } + /** TODO: Add docs */ findModuleByDocId(id: string): string | null { for (const [name, doc] of this.modules.entries()) { if (doc.guid === id) return name @@ -73,60 +79,72 @@ export class DistributedProject { return null } + /** TODO: Add docs */ async openModule(name: string): Promise { const doc = this.modules.get(name) if (doc == null) return null return await DistributedModule.load(doc) } + /** TODO: Add docs */ openUnloadedModule(name: string): DistributedModule | null { const doc = this.modules.get(name) if (doc == null) return null return new DistributedModule(doc) } + /** TODO: Add docs */ createUnloadedModule(name: string, doc: Y.Doc): DistributedModule { this.modules.set(name, doc) return new DistributedModule(doc) } + /** TODO: Add docs */ createNewModule(name: string): DistributedModule { return this.createUnloadedModule(name, new Y.Doc()) } + /** TODO: Add docs */ deleteModule(name: string): void { this.modules.delete(name) } + /** TODO: Add docs */ dispose(): void { this.doc.destroy() } } +/** TODO: Add docs */ export class ModuleDoc { ydoc: Y.Doc nodes: Y.Map + /** TODO: Add docs */ constructor(ydoc: Y.Doc) { this.ydoc = ydoc this.nodes = ydoc.getMap('nodes') } } +/** TODO: Add docs */ export class DistributedModule { doc: ModuleDoc undoManager: Y.UndoManager + /** TODO: Add docs */ static async load(ydoc: Y.Doc): Promise { ydoc.load() await ydoc.whenLoaded return new DistributedModule(ydoc) } + /** TODO: Add docs */ constructor(ydoc: Y.Doc) { this.doc = new ModuleDoc(ydoc) this.undoManager = new Y.UndoManager([this.doc.nodes]) } + /** TODO: Add docs */ dispose(): void { this.doc.ydoc.destroy() } @@ -137,10 +155,12 @@ export type LocalUserActionOrigin = (typeof localUserActionOrigins)[number] export type Origin = LocalUserActionOrigin | 'remote' | 'local:autoLayout' /** Locally-originated changes not otherwise specified. */ export const defaultLocalOrigin: LocalUserActionOrigin = 'local:userAction' +/** TODO: Add docs */ export function isLocalUserActionOrigin(origin: string): origin is LocalUserActionOrigin { const localOriginNames: readonly string[] = localUserActionOrigins return localOriginNames.includes(origin) } +/** TODO: Add docs */ export function tryAsOrigin(origin: string): Origin | undefined { if (isLocalUserActionOrigin(origin)) return origin if (origin === 'local:autoLayout') return origin @@ -151,34 +171,42 @@ export type SourceRange = readonly [start: number, end: number] declare const brandSourceRangeKey: unique symbol export type SourceRangeKey = string & { [brandSourceRangeKey]: never } +/** TODO: Add docs */ export function sourceRangeKey(range: SourceRange): SourceRangeKey { return `${range[0].toString(16)}:${range[1].toString(16)}` as SourceRangeKey } +/** TODO: Add docs */ export function sourceRangeFromKey(key: SourceRangeKey): SourceRange { return key.split(':').map(x => parseInt(x, 16)) as [number, number] } +/** TODO: Add docs */ export class IdMap { private readonly rangeToExpr: Map + /** TODO: Add docs */ constructor(entries?: [string, ExternalId][]) { this.rangeToExpr = new Map(entries ?? []) } + /** TODO: Add docs */ static Mock(): IdMap { return new IdMap([]) } + /** TODO: Add docs */ insertKnownId(range: SourceRange, id: ExternalId) { const key = sourceRangeKey(range) this.rangeToExpr.set(key, id) } + /** TODO: Add docs */ getIfExist(range: SourceRange): ExternalId | undefined { const key = sourceRangeKey(range) return this.rangeToExpr.get(key) } + /** TODO: Add docs */ getOrInsertUniqueId(range: SourceRange): ExternalId { const key = sourceRangeKey(range) const val = this.rangeToExpr.get(key) @@ -191,18 +219,22 @@ export class IdMap { } } + /** TODO: Add docs */ entries(): [SourceRangeKey, ExternalId][] { return [...this.rangeToExpr] as [SourceRangeKey, ExternalId][] } + /** TODO: Add docs */ get size(): number { return this.rangeToExpr.size } + /** TODO: Add docs */ clear(): void { this.rangeToExpr.clear() } + /** TODO: Add docs */ isEqual(other: IdMap): boolean { if (other.size !== this.size) return false for (const [key, value] of this.rangeToExpr.entries()) { @@ -212,6 +244,7 @@ export class IdMap { return true } + /** TODO: Add docs */ validate() { const uniqueValues = new Set(this.rangeToExpr.values()) if (uniqueValues.size < this.rangeToExpr.size) { @@ -219,11 +252,13 @@ export class IdMap { } } + /** TODO: Add docs */ clone(): IdMap { return new IdMap(this.entries()) } // Debugging. + /** TODO: Add docs */ compare(other: IdMap) { console.info(`IdMap.compare -------`) const allKeys = new Set() @@ -240,26 +275,32 @@ export class IdMap { } const uuidRegex = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/ +/** TODO: Add docs */ export function isUuid(x: unknown): x is Uuid { return typeof x === 'string' && x.length === 36 && uuidRegex.test(x) } +/** TODO: Add docs */ export function rangeEquals(a: SourceRange, b: SourceRange): boolean { return a[0] == b[0] && a[1] == b[1] } +/** TODO: Add docs */ export function rangeIncludes(a: SourceRange, b: number): boolean { return a[0] <= b && a[1] >= b } +/** TODO: Add docs */ export function rangeLength(a: SourceRange): number { return a[1] - a[0] } +/** TODO: Add docs */ export function rangeEncloses(a: SourceRange, b: SourceRange): boolean { return a[0] <= b[0] && a[1] >= b[1] } +/** TODO: Add docs */ export function rangeIntersects(a: SourceRange, b: SourceRange): boolean { return a[0] <= b[1] && a[1] >= b[0] } diff --git a/eslint.config.mjs b/eslint.config.mjs index d51443827fa..5da5d1ec315 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,8 @@ /** @file ESLint configuration file. */ -/** NOTE: The "Experimental: Use Flat Config" option must be enabled. - * Flat config is still not quite mature, so is disabled by default. */ +/** + * NOTE: The "Experimental: Use Flat Config" option must be enabled. + * Flat config is still not quite mature, so is disabled by default. + */ import * as path from 'node:path' import * as url from 'node:url' @@ -24,13 +26,15 @@ import globals from 'globals' const DEBUG_STATEMENTS_MESSAGE = 'Avoid leaving debugging statements when committing code' const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url)) const NAME = 'enso' -/** An explicit whitelist of CommonJS modules, which do not support namespace imports. +/** + * An explicit whitelist of CommonJS modules, which do not support namespace imports. * Many of these have incorrect types, so no type error may not mean they support ESM, * and conversely type errors may not mean they don't support ESM - * but we add those to the whitelist anyway otherwise we get type errors. * In particular, `string-length` supports ESM but its type definitions don't. * `yargs` is a modules we explicitly want the default imports of. - * `node:process` is here because `process.on` does not exist on the namespace import. */ + * `node:process` is here because `process.on` does not exist on the namespace import. + */ const DEFAULT_IMPORT_ONLY_MODULES = '@vitejs\\u002Fplugin-react|node:process|chalk|string-length|yargs|yargs\\u002Fyargs|sharp|to-ico|connect|morgan|serve-static|tiny-invariant|clsx|create-servers|electron-is-dev|fast-glob|esbuild-plugin-.+|opener|tailwindcss.*|@modyfi\\u002Fvite-plugin-yaml|build-info|is-network-error|validator.+|.*[.]json$' const RELATIVE_MODULES = @@ -48,7 +52,6 @@ const NOT_CONSTANT_CASE = `/^(?!${WHITELISTED_CONSTANTS}$|_?[A-Z][A-Z0-9]*(_[A-Z // Extracted to a variable because it needs to be used twice: // - once as-is for `.d.ts` // - once explicitly disallowing `declare`s in regular `.ts`. -/** @type {{ selector: string; message: string; }[]} */ const RESTRICTED_SYNTAXES = [ { selector: `ImportDeclaration[source.value=/^(?!(${ALLOWED_DEFAULT_IMPORT_MODULES})$)[^.]/] > ImportDefaultSpecifier`, @@ -195,6 +198,8 @@ export default [ '**/build.mjs', '**/*.timestamp-*.mjs', '**/node_modules', + '**/generated', + 'app/rust-ffi/pkg/', ], }, eslintJs.configs.recommended, @@ -207,7 +212,12 @@ export default [ tsconfigRootDir: DIR_NAME, ecmaVersion: 'latest', extraFileExtensions: ['.vue'], - projectService: true, + projectService: { + allowDefaultProject: [ + 'app/ydoc-server/vitest.config.ts', + 'app/ydoc-shared/vitest.config.ts', + ], + }, }, }, rules: {