Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
163d76f
[OP-20342] Exempt test code from any-typing rules
myabc Sep 28, 2026
8b783da
[OP-20342] Remove unused global type declarations
myabc Sep 28, 2026
388e17a
[OP-20342] Type the tablesorter jQuery plugin
myabc Sep 28, 2026
c924fbb
[OP-20342] Type hook callbacks by hook name
myabc Sep 28, 2026
61400e7
[OP-20342] Replace Function types with signatures
myabc Sep 28, 2026
545a466
[OP-20344] Remove dead updateLinkedResources
myabc Sep 28, 2026
2f815e0
[OP-20344] Type HAL links as callable links
myabc Sep 28, 2026
a771c82
[OP-20344] Type HAL resource sources
myabc Sep 28, 2026
42c611e
[OP-20344] Type HAL schemas and field schemas
myabc Sep 28, 2026
75e8ef1
[OP-20344] Declare HAL resource attributes
myabc Sep 28, 2026
50594c4
[OP-20344] Type HAL services and error resources
myabc Sep 28, 2026
a541ec5
[OP-20344] Type remaining HAL resource members
myabc Sep 28, 2026
9db9969
[OP-20344] Drop casts made redundant by HAL types
myabc Sep 28, 2026
8d2e4e0
[OP-20344] Type absent and array-valued HAL links
myabc Sep 29, 2026
9af7643
[OP-20349] Type field resource attribute reads
myabc Sep 28, 2026
982f01d
[OP-20349] Type work package attribute reads
myabc Sep 28, 2026
9b93d8b
[OP-20349] Type remaining resource attribute reads
myabc Sep 28, 2026
9464b16
[OP-20349] Type spec access to resource attributes
myabc Sep 28, 2026
2705f0a
[OP-20349] Type HalResource index as unknown
myabc Sep 28, 2026
539fd5b
[OP-20347] Type single view form attribute groups
myabc Sep 29, 2026
7617d2c
[OP-20347] Type watchers and activity collections
myabc Sep 29, 2026
ea95bb9
[OP-20347] Type relations and hierarchy payloads
myabc Sep 29, 2026
c377eae
[OP-20347] Type work package filter values
myabc Sep 29, 2026
9d74344
[OP-20347] Type work package list and edit form
myabc Sep 29, 2026
b14812d
[OP-20347] Type changeset setValue overrides
myabc Sep 29, 2026
952e159
[OP-20347] Type work package view routing
myabc Sep 29, 2026
ccf6f64
[OP-20347] Type group object values as strings
myabc Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,21 @@ export default defineConfig([
rules: {
...vitest.configs.recommended.rules,

// vitest expect(...) is always any
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',

// Allow more than one class definitions per file (test components)
'max-classes-per-file': 'off',
},
},
{
files: ['**/*.spec.ts', '**/testing/**/*.ts', 'src/stimulus/test-helpers.ts', 'src/test-*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
},
},
{
// esbuild follows imports past the tsconfig exclude, so the import site
// is the boundary keeping test helpers out of the production bundle.
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/app/core/apiv3/cache/cachable-apiv3-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from 'rxjs/operators';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { CallableHalLink } from 'core-app/features/hal/hal-link/hal-link';

export abstract class ApiV3Resource<T extends HasId = HalResource>
extends ApiV3GettableResource<T> {
Expand Down Expand Up @@ -137,15 +138,15 @@ export abstract class ApiV3Resource<T extends HasId = HalResource>
switchMap((resource) => {
if (resource.$links.schema) {
return this.schemaCache
.requireAndStream(resource.$links.schema.href)
.requireAndStream((resource.$links.schema as CallableHalLink).href!)
.pipe(
take(1),
mapTo(resource),
);
}
return of(resource);
}),
) as any; // T does not extend HalResource for virtual endpoints such as board, thus we need to cast here
) as unknown as Observable<T>; // T does not extend HalResource for virtual endpoints such as board, thus we need to cast here
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { SchemaResource } from 'core-app/features/hal/resources/schema-resource'
import { HalPayloadHelper } from 'core-app/features/hal/schemas/hal-payload.helper';
import { GridWidgetResource } from 'core-app/features/hal/resources/grid-widget-resource';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { GridResource } from 'core-app/features/hal/resources/grid-resource';

export class ApiV3GridForm extends ApiV3FormResource {
/**
Expand All @@ -42,7 +43,7 @@ export class ApiV3GridForm extends ApiV3FormResource {
*/
public static extractPayload(resource:HalResource|object, schema:SchemaResource|null = null):object {
if (resource instanceof HalResource && schema) {
const grid = resource;
const grid = resource as GridResource;
const payload = HalPayloadHelper.extractPayloadFromSchema(grid, schema);

// The widget only states the type of the widget resource but does not explain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ export class ApiV3QueriesPaths extends ApiV3ResourceCollection<QueryResource, Ap
*/
public toggleStarred(query:QueryResource):Promise<unknown> {
if (query.starred) {
return query.unstar();
return query.unstar!();
}
return query.star();
return query.star!();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/core/apiv3/virtual/apiv3-boards-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class ApiV3BoardsPaths extends ApiV3Collection<Board, ApiV3BoardPath> {
switchMap((form) => this
.apiRoot
.grids
.post(form.payload.$source)),
.post(form.payload.$source as unknown as GridResource)),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ describe('currentProject service', () => {
providers: [
CurrentProjectService,
PathHelperService,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
{ provide: ApiV3Service, useValue: apiV3Stub },
],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
//++

import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { HalSource } from 'core-app/features/hal/interfaces';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { GlobalSearchInputComponent } from './global-search-input.component';

Expand Down Expand Up @@ -62,7 +63,7 @@ describe('GlobalSearchInputComponent#followItem', () => {
// so followItem exercises the production displayId getter rather than a stub.
function buildWorkPackage(source:{ id:number, displayId?:string }):WorkPackageResource {
const item = Object.create(WorkPackageResource.prototype) as WorkPackageResource;
item.$source = source;
item.$source = source as unknown as HalSource;
return item;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
if (item instanceof WorkPackageResource) {
window.location.href = this.wpPath(item.displayId);
} else if (item) {
this.searchInScope(item.projectScope);
this.searchInScope((item as SearchOptionItem).projectScope);
}
}

Expand Down
3 changes: 1 addition & 2 deletions frontend/src/app/core/path-helper/apiv3-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

import { ApiV3FilterBuilder } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';

export class ApiV3Paths {
readonly apiV3Base:string;
Expand Down Expand Up @@ -96,7 +95,7 @@ export class ApiV3Paths {

if (!workPackage.id || workPackage.id === 'new') {
// that are members of that project:
filters.add('member', '=', [(workPackage.project as HalResource).id!]);
filters.add('member', '=', [workPackage.project.id!]);
} else {
// that are mentionable on the work package
filters.add(
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/core/schemas/schema-cache.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { HalResourceService } from 'core-app/features/hal/services/hal-resource.
import { WorkPackageSchemaProxy } from 'core-app/features/hal/schemas/work-package-schema-proxy';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { SchemaResource } from 'core-app/features/hal/resources/schema-resource';
import { CallableHalLink } from 'core-app/features/hal/hal-link/hal-link';

export const fallbackSchemaId = '__fallback';

Expand Down Expand Up @@ -82,8 +83,7 @@ export class SchemaCacheService extends StateCacheService<SchemaResource> {
}

public getSchemaHref(resource:HalResource):string|undefined {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return resource.$links.schema?.href as string|undefined;
return (resource.$links.schema as CallableHalLink|undefined)?.href as string|undefined;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
} from 'rxjs/operators';

import { I18nService } from 'core-app/core/i18n/i18n.service';
import { HalLink } from 'core-app/features/hal/hal-link/hal-link';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { AttachmentsStore } from 'core-app/core/state/attachments/attachments.store';
Expand All @@ -58,6 +57,7 @@ import {
import { LazyInject } from 'core-app/shared/helpers/angular/lazy-inject.decorator';
import isNewResource, { HAL_NEW_RESOURCE_ID } from 'core-app/features/hal/helpers/is-new-resource';
import waitForUploadsFinished from 'core-app/core/upload/wait-for-uploads-finished';
import { CallableHalLink } from 'core-app/features/hal/hal-link/hal-link';

@Injectable()
export class AttachmentsResourceService extends ResourceStoreService<IAttachment> {
Expand Down Expand Up @@ -202,10 +202,10 @@ export class AttachmentsResourceService extends ResourceStoreService<IAttachment
}

private getDirectUploadLink(resource:HalResource):string|null {
const links = resource.$links as { prepareAttachment:HalLink };
const links = resource.$links;

if (links.prepareAttachment) {
return links.prepareAttachment.href!;
return (links.prepareAttachment as CallableHalLink).href!;
}

if (isNewResource(resource)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,21 @@ export class BcfWpAttributeGroupComponent extends UntilDestroyedMixin implements
.id(this.workPackage)
.requireAndStream()
.pipe(this.untilDestroyed())
.subscribe(async (wp) => {
this.workPackage = wp;
.subscribe((wp) => {
void this.onWorkPackageChange(wp);
});
}

if (!this.projectId) {
await this.initialize(this.workPackage);
}
private async onWorkPackageChange(wp:WorkPackageResource) {
this.workPackage = wp;

if (wp.bcfViewpoints) {
this.refreshViewpoints(wp.bcfViewpoints);
}
});
if (!this.projectId) {
await this.initialize(this.workPackage);
}

if (wp.bcfViewpoints) {
this.refreshViewpoints(wp.bcfViewpoints as unknown as HalLink[]);
}
}

async initialize(workPackage:WorkPackageResource) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ViewpointsService {
@LazyInject() apiV3Service:ApiV3Service;

public getViewPointResource(workPackage:WorkPackageResource, index:number):BcfViewpointPaths {
const viewpointHref = (workPackage.bcfViewpoints as HalResource[])[index].href!;
const viewpointHref = workPackage.bcfViewpoints![index].href!;

return this.bcfApi.parse<BcfViewpointPaths>(viewpointHref)!;
}
Expand Down Expand Up @@ -91,7 +91,7 @@ export class ViewpointsService {
}

public saveViewpoint$(workPackage:WorkPackageResource, viewpoint?:CreateBcfViewpointData):Observable<CreateBcfViewpointData> {
const projectLink = (workPackage.project as HalResource).href;
const projectLink = workPackage.project.href;
const wpProjectId = idFromLink(projectLink);
const topicUUID$ = this.setBcfTopic$(workPackage);
// Default to the current viewer's viewpoint
Expand Down Expand Up @@ -137,7 +137,7 @@ export class ViewpointsService {

private createBcfTopic$(workPackage:WorkPackageResource):Observable<string> {
const wpProjectId = idFromLink(workPackage.project.href);
const wpPayload = workPackage.convertBCF.payload;
const wpPayload = (workPackage.convertBCF as HalResource).payload;

return this.bcfApi
.projects.id(wpProjectId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
IfcProjectDefinition,
} from 'core-app/features/bim/ifc_models/pages/viewer/ifc-models-data.service';
import { BcfViewpointData, CreateBcfViewpointData } from 'core-app/features/bim/bcf/api/bcf-api.model';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
import { getMetaContent } from 'core-app/core/setup/globals/global-helpers';

Expand Down Expand Up @@ -256,7 +255,7 @@ export class IFCViewerService extends ViewerBridgeService {
// no means of setting it from here. Hence we must make a hard transition to bcf details route of the
// current work package.
window.location.href = this.pathHelper.bimDetailsPath(
idFromLink((workPackage.project as HalResource).href),
idFromLink(workPackage.project.href),
workPackage.id || '',
index,
);
Expand Down
28 changes: 14 additions & 14 deletions frontend/src/app/features/hal/hal-link/hal-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface HalLinkInterface {
method:HTTPSupportedMethods;
title?:string;
templated?:boolean;
payload?:any;
payload?:unknown;
type?:string;
identifier?:string;
displayId?:string;
Expand All @@ -50,18 +50,21 @@ export interface HalLinkSource {
title:string;
}

export interface CallableHalLink extends HalLinkInterface {
$link:this;
export interface CallableHalLink<T = HalResource> extends HalLinkInterface {
(data?:unknown, headers?:HTTPClientHeaders):Promise<T>;
$link:HalLink;
data?:Promise<HalResource>;
}

export type HalLinkRequestMethod = (method:HTTPSupportedMethods, href:string, data?:unknown, headers?:HTTPClientHeaders) => Promise<HalResource>;

export class HalLink implements HalLinkInterface {
constructor(public requestMethod:(method:HTTPSupportedMethods, href:string, data:any, headers:any) => Promise<HalResource>,
constructor(public requestMethod:HalLinkRequestMethod,
public href:string|null = null,
public title = '',
public method:HTTPSupportedMethods = 'get',
public templated = false,
public payload?:any,
public payload?:unknown,
public type = 'application/json',
public identifier?:string,
public displayId?:string) {
Expand All @@ -72,7 +75,7 @@ export class HalLink implements HalLinkInterface {
*/
public static fromObject(halResourceService:HalResourceService, link:HalLinkInterface):HalLink {
return new HalLink(
(method:HTTPSupportedMethods, href:string, data:object, headers:HTTPClientHeaders) => firstValueFrom(halResourceService.request(method, href, data, headers)),
(method, href, data, headers) => firstValueFrom(halResourceService.request(method, href, data, headers)),
link.href,
link.title,
link.method,
Expand All @@ -87,9 +90,8 @@ export class HalLink implements HalLinkInterface {
/**
* Fetch the resource.
*/
public $fetch(...params:any[]):Promise<HalResource> {
const [data, headers] = params;
return this.requestMethod(this.method, this.href!, data, headers);
public $fetch<T = HalResource>(data?:unknown, headers?:HTTPClientHeaders):Promise<T> {
return this.requestMethod(this.method, this.href!, data, headers) as Promise<T>;
}

/**
Expand Down Expand Up @@ -126,10 +128,10 @@ export class HalLink implements HalLinkInterface {
*
* @returns {CallableHalLink}
*/
public $callable():CallableHalLink {
const linkFunc:any = (...params:any[]) => this.$fetch(...params);
public $callable<T = HalResource>():CallableHalLink<T> {
const linkFunc = (data?:unknown, headers?:HTTPClientHeaders) => this.$fetch<T>(data, headers);

Object.assign(linkFunc, {
return Object.assign(linkFunc, {
$link: this,
href: this.href,
title: this.title,
Expand All @@ -140,7 +142,5 @@ export class HalLink implements HalLinkInterface {
identifier: this.identifier,
displayId: this.displayId,
});

return linkFunc;
}
}
Loading
Loading