diff --git a/api-editor/gui/src/app/App.tsx b/api-editor/gui/src/app/App.tsx index 7d8510b39..96f88d7f0 100644 --- a/api-editor/gui/src/app/App.tsx +++ b/api-editor/gui/src/app/App.tsx @@ -36,7 +36,6 @@ import { initializeUI, persistUI, selectCurrentUserAction, - selectFilter, selectShowAnnotationImportDialog, selectShowAPIImportDialog, selectShowUsageImportDialog, @@ -44,21 +43,14 @@ import { setFilterString, } from '../features/ui/uiSlice'; import { initializeUsages, persistUsages, selectUsages } from '../features/usages/usageSlice'; -import { - initializePythonPackage, - selectFilteredPythonPackage, - selectPythonPackage, -} from '../features/packageData/apiSlice'; +import { initializePythonPackage, selectRawPythonPackage } from '../features/packageData/apiSlice'; import { PythonClass } from '../features/packageData/model/PythonClass'; import { PythonParameter } from '../features/packageData/model/PythonParameter'; export const App: React.FC = function () { useIndexedDB(); - const pythonPackage = useAppSelector(selectPythonPackage); - const usages = useAppSelector(selectUsages); - const pythonFilter = useAppSelector(selectFilter); - const filteredPythonPackage = useAppSelector(selectFilteredPythonPackage); + const pythonPackage = useAppSelector(selectRawPythonPackage); const [showInferErrorDialog, setShowInferErrorDialog] = useState(false); const [inferErrors, setInferErrors] = useState([]); @@ -68,7 +60,7 @@ export const App: React.FC = function () { }; const currentUserAction = useAppSelector(selectCurrentUserAction); - const userActionTarget = pythonPackage.getByRelativePathAsString(currentUserAction.target); + const userActionTarget = pythonPackage.getDeclarationById(currentUserAction.target); const showAnnotationImportDialog = useAppSelector(selectShowAnnotationImportDialog); const showAPIImportDialog = useAppSelector(selectShowAPIImportDialog); const showUsagesImportDialog = useAppSelector(selectShowUsageImportDialog); @@ -125,9 +117,7 @@ export const App: React.FC = function () { /> )} {currentUserAction.type === 'move' && } - {currentUserAction.type === 'none' && ( - - )} + {currentUserAction.type === 'none' && } {currentUserAction.type === 'optional' && ( )} @@ -135,7 +125,7 @@ export const App: React.FC = function () { {currentUserAction.type === 'todo' && } - + {showAnnotationImportDialog && } diff --git a/api-editor/gui/src/common/GenerateAdapters.tsx b/api-editor/gui/src/common/GenerateAdapters.tsx index 853bf6de0..6fe5b03da 100644 --- a/api-editor/gui/src/common/GenerateAdapters.tsx +++ b/api-editor/gui/src/common/GenerateAdapters.tsx @@ -18,7 +18,7 @@ import React, { useRef, useState } from 'react'; import { useAppSelector } from '../app/hooks'; import { selectAnnotations } from '../features/annotations/annotationSlice'; import { AnnotatedPythonPackageBuilder } from '../features/annotatedPackageData/model/AnnotatedPythonPackageBuilder'; -import { selectPythonPackage } from '../features/packageData/apiSlice'; +import { selectRawPythonPackage } from '../features/packageData/apiSlice'; interface GenerateAdaptersProps { displayInferErrors: (errors: string[]) => void; @@ -31,7 +31,7 @@ export const GenerateAdapters: React.FC = function ({ dis const cancelRef = useRef(null); const annotationStore = useAppSelector(selectAnnotations); - const pythonPackage = useAppSelector(selectPythonPackage); + const pythonPackage = useAppSelector(selectRawPythonPackage); const packageNameIsValid = !shouldValidate || newPackageName !== ''; diff --git a/api-editor/gui/src/features/annotatedPackageData/model/AnnotatedPythonPackageBuilder.ts b/api-editor/gui/src/features/annotatedPackageData/model/AnnotatedPythonPackageBuilder.ts index 2213195ea..cc219a22c 100644 --- a/api-editor/gui/src/features/annotatedPackageData/model/AnnotatedPythonPackageBuilder.ts +++ b/api-editor/gui/src/features/annotatedPackageData/model/AnnotatedPythonPackageBuilder.ts @@ -17,6 +17,7 @@ import { InferableBoundaryAnnotation, InferableCalledAfterAnnotation, InferableConstantAnnotation, + InferableDescriptionAnnotation, InferableEnumAnnotation, InferableGroupAnnotation, InferableMoveAnnotation, @@ -25,7 +26,6 @@ import { InferableRemoveAnnotation, InferableRenameAnnotation, InferableRequiredAnnotation, - InferableDescriptionAnnotation, InferableTodoAnnotation, } from './InferableAnnotation'; @@ -44,7 +44,7 @@ export class AnnotatedPythonPackageBuilder { this.pythonPackage.distribution, this.pythonPackage.version, this.#buildAnnotatedPythonModules(this.pythonPackage.modules), - this.#getExistingAnnotations(this.pythonPackage.pathAsString()), + this.#getExistingAnnotations(this.pythonPackage.id), ); } @@ -57,7 +57,7 @@ export class AnnotatedPythonPackageBuilder { pythonModule.fromImports, this.#buildAnnotatedPythonClasses(pythonModule), this.#buildAnnotatedPythonFunctions(pythonModule), - this.#getExistingAnnotations(pythonModule.pathAsString()), + this.#getExistingAnnotations(pythonModule.id), ), ); } @@ -74,7 +74,7 @@ export class AnnotatedPythonPackageBuilder { pythonClass.isPublic, pythonClass.description, pythonClass.fullDocstring, - this.#getExistingAnnotations(pythonClass.pathAsString()), + this.#getExistingAnnotations(pythonClass.id), ), ); } @@ -101,7 +101,7 @@ export class AnnotatedPythonPackageBuilder { pythonFunction.isPublic, pythonFunction.description, pythonFunction.fullDocstring, - this.#getExistingAnnotations(pythonFunction.pathAsString()), + this.#getExistingAnnotations(pythonFunction.id), ); } @@ -119,7 +119,7 @@ export class AnnotatedPythonPackageBuilder { pythonParameter.isPublic, pythonParameter.typeInDocs, pythonParameter.description, - this.#getExistingAnnotations(pythonParameter.pathAsString()), + this.#getExistingAnnotations(pythonParameter.id), ), ); } @@ -132,7 +132,7 @@ export class AnnotatedPythonPackageBuilder { pythonResult.typeInDocs, pythonResult.typeInDocs, pythonResult.description, - this.#getExistingAnnotations(pythonResult.pathAsString()), + this.#getExistingAnnotations(pythonResult.id), ), ); } diff --git a/api-editor/gui/src/features/annotations/forms/AttributeForm.tsx b/api-editor/gui/src/features/annotations/forms/AttributeForm.tsx index de01fb472..27ab5e462 100644 --- a/api-editor/gui/src/features/annotations/forms/AttributeForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/AttributeForm.tsx @@ -9,7 +9,7 @@ interface AttributeFormProps { } export const AttributeForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; // Hooks ----------------------------------------------------------------------------------------------------------- const previousAnnotation = useAppSelector(selectAttribute(targetPath)); diff --git a/api-editor/gui/src/features/annotations/forms/BoundaryForm.tsx b/api-editor/gui/src/features/annotations/forms/BoundaryForm.tsx index 280e70420..00750c325 100644 --- a/api-editor/gui/src/features/annotations/forms/BoundaryForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/BoundaryForm.tsx @@ -52,7 +52,7 @@ const initialFormState = function (previousInterval: Optional): Bounda }; export const BoundaryForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const prevInterval = useAppSelector(selectBoundary(targetPath))?.interval; // Hooks ----------------------------------------------------------------------------------------------------------- diff --git a/api-editor/gui/src/features/annotations/forms/CalledAfterForm.tsx b/api-editor/gui/src/features/annotations/forms/CalledAfterForm.tsx index 730a7ca74..08a94329a 100644 --- a/api-editor/gui/src/features/annotations/forms/CalledAfterForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/CalledAfterForm.tsx @@ -16,7 +16,7 @@ interface CalledAfterFormState { } export const CalledAfterForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const currentCalledAfters = Object.keys(useAppSelector(selectCalledAfters(targetPath))); const remainingCalledAfters = target diff --git a/api-editor/gui/src/features/annotations/forms/ConstantForm.tsx b/api-editor/gui/src/features/annotations/forms/ConstantForm.tsx index c44841921..fc6349723 100644 --- a/api-editor/gui/src/features/annotations/forms/ConstantForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/ConstantForm.tsx @@ -9,7 +9,7 @@ interface ConstantFormProps { } export const ConstantForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; // Hooks ----------------------------------------------------------------------------------------------------------- const constantDefinition = useAppSelector(selectConstant(targetPath)); diff --git a/api-editor/gui/src/features/annotations/forms/DescriptionForm.tsx b/api-editor/gui/src/features/annotations/forms/DescriptionForm.tsx index 0e05f0888..70062e455 100644 --- a/api-editor/gui/src/features/annotations/forms/DescriptionForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/DescriptionForm.tsx @@ -18,7 +18,7 @@ interface DescriptionFormState { } export const DescriptionForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const prevNewDescription = useAppSelector(selectDescription(targetPath))?.newDescription; const oldDescription = target.description; diff --git a/api-editor/gui/src/features/annotations/forms/EnumForm.tsx b/api-editor/gui/src/features/annotations/forms/EnumForm.tsx index ed4eb43d7..5b82d8238 100644 --- a/api-editor/gui/src/features/annotations/forms/EnumForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/EnumForm.tsx @@ -31,10 +31,10 @@ interface EnumFormState { } export const EnumForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; // Hooks ----------------------------------------------------------------------------------------------------------- - const enumDefinition = useAppSelector(selectEnum(target.pathAsString())); + const enumDefinition = useAppSelector(selectEnum(target.id)); const dispatch = useAppDispatch(); const { diff --git a/api-editor/gui/src/features/annotations/forms/GroupForm.tsx b/api-editor/gui/src/features/annotations/forms/GroupForm.tsx index 6dea79a8d..169b96584 100644 --- a/api-editor/gui/src/features/annotations/forms/GroupForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/GroupForm.tsx @@ -22,7 +22,7 @@ interface GroupFormState { } export const GroupForm: React.FC = function ({ target, groupName }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const currentGroups = useAppSelector(selectGroups(targetPath)); let prevGroupAnnotation: GroupAnnotation | undefined; if (groupName && currentGroups) { diff --git a/api-editor/gui/src/features/annotations/forms/MoveForm.tsx b/api-editor/gui/src/features/annotations/forms/MoveForm.tsx index 5121bbd85..974ebf459 100644 --- a/api-editor/gui/src/features/annotations/forms/MoveForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/MoveForm.tsx @@ -17,7 +17,7 @@ interface MoveFormState { } export const MoveForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const prevDestination = useAppSelector(selectMove(targetPath))?.destination; const oldModulePath = target?.parent()?.name; diff --git a/api-editor/gui/src/features/annotations/forms/OptionalForm.tsx b/api-editor/gui/src/features/annotations/forms/OptionalForm.tsx index 264f95b56..ef21d1a15 100644 --- a/api-editor/gui/src/features/annotations/forms/OptionalForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/OptionalForm.tsx @@ -9,7 +9,7 @@ interface OptionalFormProps { } export const OptionalForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; // Hooks ----------------------------------------------------------------------------------------------------------- const optionalDefinition = useAppSelector(selectOptional(targetPath)); diff --git a/api-editor/gui/src/features/annotations/forms/RenameForm.tsx b/api-editor/gui/src/features/annotations/forms/RenameForm.tsx index 9f5a9c014..6b4605cef 100644 --- a/api-editor/gui/src/features/annotations/forms/RenameForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/RenameForm.tsx @@ -17,7 +17,7 @@ interface RenameFormState { } export const RenameForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const prevNewName = useAppSelector(selectRenaming(targetPath))?.newName; const oldName = target.name; diff --git a/api-editor/gui/src/features/annotations/forms/TodoForm.tsx b/api-editor/gui/src/features/annotations/forms/TodoForm.tsx index ee44484a3..b2509858d 100644 --- a/api-editor/gui/src/features/annotations/forms/TodoForm.tsx +++ b/api-editor/gui/src/features/annotations/forms/TodoForm.tsx @@ -16,7 +16,7 @@ interface TodoFormState { } export const TodoForm: React.FC = function ({ target }) { - const targetPath = target.pathAsString(); + const targetPath = target.id; const prevNewTodo = useAppSelector(selectTodo(targetPath))?.newTodo; // Hooks ----------------------------------------------------------------------------------------------------------- diff --git a/api-editor/gui/src/features/packageData/apiSlice.ts b/api-editor/gui/src/features/packageData/apiSlice.ts index 1d2e12436..8cd348a6d 100644 --- a/api-editor/gui/src/features/packageData/apiSlice.ts +++ b/api-editor/gui/src/features/packageData/apiSlice.ts @@ -1,4 +1,4 @@ -import { createAsyncThunk, createSlice, PayloadAction, createSelector } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { RootState } from '../../app/store'; import { PythonPackage } from './model/PythonPackage'; import { parsePythonPackageJson, PythonPackageJson } from './model/PythonPackageBuilder'; @@ -6,8 +6,8 @@ import * as idb from 'idb-keyval'; import { selectFilter, selectSortingMode, SortingMode } from '../ui/uiSlice'; import { selectUsages } from '../usages/usageSlice'; import { selectAnnotations } from '../annotations/annotationSlice'; -import { PythonModule } from './model/PythonModule'; -import { PythonClass } from './model/PythonClass'; +import { PythonDeclaration } from './model/PythonDeclaration'; +import { UsageCountStore } from '../usages/model/UsageCountStore'; export interface APIState { pythonPackage: PythonPackage; @@ -63,64 +63,45 @@ export const { setPythonPackage, resetPythonPackage } = actions; export const apiReducer = reducer; const selectAPI = (state: RootState) => state.api; -export const selectPythonPackage = (state: RootState) => selectAPI(state).pythonPackage; -const selectSortedPythonPackages = createSelector( - [selectPythonPackage, selectSortingMode, selectUsages], - (pythonPackage, sortingMode, usages) => { - switch (sortingMode) { - case SortingMode.Alphabetical: - return pythonPackage; - case SortingMode.Usages: // Descending - return new PythonPackage( - pythonPackage.distribution, - pythonPackage.name, - pythonPackage.version, - pythonPackage.modules - .map( - (module) => - new PythonModule( - module.id, - module.name, - module.imports, - module.fromImports, - module.classes - .map( - (cls) => - new PythonClass( - cls.id, - cls.name, - cls.qualifiedName, - cls.decorators, - cls.superclasses, - cls.methods.sort( - (a, b) => - (usages.functionUsages.get(b.id) ?? 0) - - (usages.functionUsages.get(a.id) ?? 0), - ), - cls.isPublic, - cls.description, - cls.fullDocstring, - ), - ) - .sort( - (a, b) => - (usages.classUsages.get(b.id) ?? 0) - - (usages.classUsages.get(a.id) ?? 0), - ), - [...module.functions].sort( - (a, b) => - (usages.functionUsages.get(b.id) ?? 0) - - (usages.functionUsages.get(a.id) ?? 0), - ), - ), - ) - .sort((a, b) => (usages.moduleUsages.get(b.id) ?? 0) - (usages.moduleUsages.get(a.id) ?? 0)), - ); - } - }, -); +export const selectRawPythonPackage = (state: RootState) => selectAPI(state).pythonPackage; +// const selectSortedPythonPackages = createSelector( +// [selectRawPythonPackage, selectSortingMode, selectUsages], +// (pythonPackage, sortingMode, usages) => { +// switch (sortingMode) { +// case SortingMode.Alphabetical: +// return pythonPackage; +// case SortingMode.Usages: // Descending +// return pythonPackage.shallowCopy({ +// modules: [...pythonPackage.modules] +// .map((module) => +// module.shallowCopy({ +// classes: [...module.classes] +// .map((cls) => +// cls.shallowCopy({ +// methods: [...cls.methods].sort( +// (a, b) => +// (usages.functionUsages.get(b.id) ?? 0) - +// (usages.functionUsages.get(a.id) ?? 0), +// ), +// }), +// ) +// .sort( +// (a, b) => +// (usages.classUsages.get(b.id) ?? 0) - (usages.classUsages.get(a.id) ?? 0), +// ), +// functions: [...module.functions].sort( +// (a, b) => +// (usages.functionUsages.get(b.id) ?? 0) - (usages.functionUsages.get(a.id) ?? 0), +// ), +// }), +// ) +// .sort((a, b) => (usages.moduleUsages.get(b.id) ?? 0) - (usages.moduleUsages.get(a.id) ?? 0)), +// }); +// } +// }, +// ); export const selectFilteredPythonPackage = createSelector( - [selectSortedPythonPackages, selectAnnotations, selectUsages, selectFilter], + [selectRawPythonPackage, selectAnnotations, selectUsages, selectFilter], (pythonPackage, annotations, usages, filter) => { return filter.applyToPackage(pythonPackage, annotations, usages); }, @@ -137,3 +118,31 @@ export const selectNumberOfMatchedNodes = createSelector( return result; }, ); +export const selectFlatSortedDeclarationList = createSelector( + [selectFilteredPythonPackage, selectSortingMode, selectUsages], + (pythonPackage, sortingMode, usages) => { + switch (sortingMode) { + case SortingMode.Alphabetical: + return walkChildrenInPreorder(pythonPackage, sortAlphabetically); + case SortingMode.Usages: // Descending + return walkChildrenInPreorder(pythonPackage, sortByUsages(usages)); + } + }, +); + +const walkChildrenInPreorder = function ( + declaration: PythonDeclaration, + sorter: (a: PythonDeclaration, b: PythonDeclaration) => number, +): PythonDeclaration[] { + return [...declaration.children()].sort(sorter).flatMap((it) => { + return [it, ...walkChildrenInPreorder(it, sorter)]; + }); +}; + +const sortAlphabetically = (a: PythonDeclaration, b: PythonDeclaration) => { + return a.name.localeCompare(b.name); +}; + +const sortByUsages = (usages: UsageCountStore) => (a: PythonDeclaration, b: PythonDeclaration) => { + return usages.getUsageCount(b) - usages.getUsageCount(a); +}; diff --git a/api-editor/gui/src/features/packageData/model/PythonClass.test.ts b/api-editor/gui/src/features/packageData/model/PythonClass.test.ts index b444942ab..5fc41a4c8 100644 --- a/api-editor/gui/src/features/packageData/model/PythonClass.test.ts +++ b/api-editor/gui/src/features/packageData/model/PythonClass.test.ts @@ -1,43 +1,4 @@ import { PythonClass } from './PythonClass'; -import { PythonFunction } from './PythonFunction'; -import { PythonModule } from './PythonModule'; -import { PythonPackage } from './PythonPackage'; -import { PythonParameter } from './PythonParameter'; - -test('path without parent', () => { - const pythonClass = new PythonClass('Class', 'Class', 'Class'); - expect(pythonClass.path()).toEqual(['Class']); -}); - -test('path with ancestors', () => { - const pythonClass = new PythonClass('Class', 'Class', 'Class'); - - // eslint-disable-next-line no-new - new PythonPackage('distribution', 'package', '0.0.1', [ - new PythonModule('module', 'module', [], [], [pythonClass]), - ]); - - expect(pythonClass.path()).toEqual(['package', 'module', 'Class']); -}); - -test('getByRelativePath with correct path', () => { - const pythonParameter = new PythonParameter('param', 'param', 'param'); - const pythonClass = new PythonClass( - 'Class', - 'Class', - 'Class', - [], - [], - [new PythonFunction('function', 'function', 'function', [], [pythonParameter])], - ); - expect(pythonClass.getByRelativePath(['function', 'param'])).toBe(pythonParameter); -}); - -test('getByRelativePath with misleading path', () => { - const pythonClass = new PythonClass('Class', 'Class', 'Class'); - // eslint-disable-next-line testing-library/prefer-presence-queries - expect(pythonClass.getByRelativePath(['child'])).toBeNull(); -}); test('toString without decorators and superclasses', () => { const pythonClass = new PythonClass('Class', 'Class', 'Class'); diff --git a/api-editor/gui/src/features/packageData/model/PythonClass.ts b/api-editor/gui/src/features/packageData/model/PythonClass.ts index f43f95069..56bd4b5f1 100644 --- a/api-editor/gui/src/features/packageData/model/PythonClass.ts +++ b/api-editor/gui/src/features/packageData/model/PythonClass.ts @@ -3,6 +3,19 @@ import { PythonDeclaration } from './PythonDeclaration'; import { PythonFunction } from './PythonFunction'; import { PythonModule } from './PythonModule'; +interface PythonClassShallowCopy { + id?: string; + name?: string; + qualifiedName?: string; + decorators?: string[]; + superclasses?: string[]; + methods?: PythonFunction[]; + isPublic?: boolean; + reexportedBy?: string[]; + description?: string; + fullDocstring?: string; +} + export class PythonClass extends PythonDeclaration { containingModule: Optional; @@ -14,8 +27,9 @@ export class PythonClass extends PythonDeclaration { readonly superclasses: string[] = [], readonly methods: PythonFunction[] = [], readonly isPublic: boolean = true, - readonly description = '', - readonly fullDocstring = '', + readonly reexportedBy: string[] = [], + readonly description: string = '', + readonly fullDocstring: string = '', ) { super(); @@ -34,8 +48,30 @@ export class PythonClass extends PythonDeclaration { return this.methods; } - isPublicDeclaration(): boolean { - return this.isPublic; + shallowCopy({ + id = this.id, + name = this.name, + qualifiedName = this.qualifiedName, + decorators = this.decorators, + superclasses = this.superclasses, + methods = this.methods, + isPublic = this.isPublic, + reexportedBy = this.reexportedBy, + description = this.description, + fullDocstring = this.fullDocstring, + }: PythonClassShallowCopy = {}): PythonClass { + return new PythonClass( + id, + name, + qualifiedName, + decorators, + superclasses, + methods, + isPublic, + reexportedBy, + description, + fullDocstring, + ); } toString(): string { diff --git a/api-editor/gui/src/features/packageData/model/PythonDeclaration.ts b/api-editor/gui/src/features/packageData/model/PythonDeclaration.ts index 9b97f9cbc..e6bf46062 100644 --- a/api-editor/gui/src/features/packageData/model/PythonDeclaration.ts +++ b/api-editor/gui/src/features/packageData/model/PythonDeclaration.ts @@ -1,8 +1,9 @@ -import { isEmptyList } from '../../../common/util/listOperations'; import { Optional } from '../../../common/util/types'; export abstract class PythonDeclaration { + abstract readonly id: string; abstract readonly name: string; + abstract readonly isPublic: boolean; abstract parent(): Optional; @@ -12,47 +13,10 @@ export abstract class PythonDeclaration { return this.name; } - isPublicDeclaration(): boolean { - return true; - } - *descendantsOrSelf(): Generator { yield this; for (const child of this.children()) { yield* child.descendantsOrSelf(); } } - - path(): string[] { - let current: Optional = this; - const result: string[] = []; - - while (current !== null && current !== undefined) { - result.unshift(current.getUniqueName()); - current = current.parent(); - } - - return result; - } - - pathAsString(): string { - return this.path().join('/'); - } - - getByRelativePath(relativePath: string[]): Optional { - if (isEmptyList(relativePath)) { - return this; - } - - const [head, ...tail] = relativePath; - return ( - this.children() - .find((it) => it.getUniqueName() === head) - ?.getByRelativePath(tail) ?? null - ); - } - - getByRelativePathAsString(relativePath: string): Optional { - return this.getByRelativePath(relativePath.split('/').slice(1)); - } } diff --git a/api-editor/gui/src/features/packageData/model/PythonFunction.test.ts b/api-editor/gui/src/features/packageData/model/PythonFunction.test.ts index e75ab524c..93cb27ef1 100644 --- a/api-editor/gui/src/features/packageData/model/PythonFunction.test.ts +++ b/api-editor/gui/src/features/packageData/model/PythonFunction.test.ts @@ -1,43 +1,6 @@ -import { PythonClass } from './PythonClass'; import { PythonFunction } from './PythonFunction'; -import { PythonModule } from './PythonModule'; -import { PythonPackage } from './PythonPackage'; import { PythonParameter } from './PythonParameter'; -test('path without parent', () => { - const pythonFunction = new PythonFunction('function', 'function', 'function'); - expect(pythonFunction.path()).toEqual(['function']); -}); - -test('path with ancestors', () => { - const pythonFunction = new PythonFunction('function', 'function', 'function'); - - // eslint-disable-next-line no-new - new PythonPackage('distribution', 'package', '0.0.1', [ - new PythonModule( - 'module', - 'module', - [], - [], - [new PythonClass('Class', 'Class', 'Class', [], [], [pythonFunction])], - ), - ]); - - expect(pythonFunction.path()).toEqual(['package', 'module', 'Class', 'function']); -}); - -test('getByRelativePath with correct path', () => { - const pythonParameter = new PythonParameter('param', 'param', 'param'); - const pythonFunction = new PythonFunction('function', 'function', 'function', [], [pythonParameter]); - expect(pythonFunction.getByRelativePath(['param'])).toBe(pythonParameter); -}); - -test('getByRelativePath with misleading path', () => { - const pythonFunction = new PythonFunction('function', 'function', 'function'); - // eslint-disable-next-line testing-library/prefer-presence-queries - expect(pythonFunction.getByRelativePath(['child'])).toBeNull(); -}); - test('toString without decorators and parameters', () => { const pythonFunction = new PythonFunction('function', 'function', 'function'); expect(pythonFunction.toString()).toBe('def function()'); diff --git a/api-editor/gui/src/features/packageData/model/PythonFunction.ts b/api-editor/gui/src/features/packageData/model/PythonFunction.ts index bb73b2a9d..203c648cd 100644 --- a/api-editor/gui/src/features/packageData/model/PythonFunction.ts +++ b/api-editor/gui/src/features/packageData/model/PythonFunction.ts @@ -2,9 +2,22 @@ import { Optional } from '../../../common/util/types'; import { PythonClass } from './PythonClass'; import { PythonDeclaration } from './PythonDeclaration'; import { PythonModule } from './PythonModule'; -import { PythonParameter } from './PythonParameter'; +import { PythonParameter, PythonParameterAssignment } from './PythonParameter'; import { PythonResult } from './PythonResult'; +interface PythonFunctionShallowCopy { + id?: string; + name?: string; + qualifiedName?: string; + decorators?: string[]; + parameters?: PythonParameter[]; + results?: PythonResult[]; + isPublic?: boolean; + reexportedBy?: string[]; + description?: string; + fullDocstring?: string; +} + export class PythonFunction extends PythonDeclaration { containingModuleOrClass: Optional; @@ -16,6 +29,7 @@ export class PythonFunction extends PythonDeclaration { readonly parameters: PythonParameter[] = [], readonly results: PythonResult[] = [], readonly isPublic: boolean = false, + readonly reexportedBy: string[] = [], readonly description = '', readonly fullDocstring = '', ) { @@ -40,10 +54,6 @@ export class PythonFunction extends PythonDeclaration { return this.parameters; } - isPublicDeclaration(): boolean { - return this.isPublic; - } - getUniqueName(): string { const segments = this.id.split('/'); return segments[segments.length - 1]; @@ -62,11 +72,7 @@ export class PythonFunction extends PythonDeclaration { } explicitParameters(): PythonParameter[] { - if (this.parent() instanceof PythonModule) { - return this.children(); - } - - return this.children().slice(1); + return this.parameters.filter((it) => it.assignedBy !== PythonParameterAssignment.IMPLICIT); } siblingFunctions(): PythonFunction[] { @@ -77,6 +83,32 @@ export class PythonFunction extends PythonDeclaration { ); } + shallowCopy({ + id = this.id, + name = this.name, + qualifiedName = this.qualifiedName, + decorators = this.decorators, + parameters = this.parameters, + results = this.results, + isPublic = this.isPublic, + reexportedBy = this.reexportedBy, + description = this.description, + fullDocstring = this.fullDocstring, + }: PythonFunctionShallowCopy = {}): PythonFunction { + return new PythonFunction( + id, + name, + qualifiedName, + decorators, + parameters, + results, + isPublic, + reexportedBy, + description, + fullDocstring, + ); + } + toString(): string { let result = ''; diff --git a/api-editor/gui/src/features/packageData/model/PythonModule.test.ts b/api-editor/gui/src/features/packageData/model/PythonModule.test.ts index 90a30b9f7..bbd83c43e 100644 --- a/api-editor/gui/src/features/packageData/model/PythonModule.test.ts +++ b/api-editor/gui/src/features/packageData/model/PythonModule.test.ts @@ -1,40 +1,4 @@ -import { PythonFunction } from './PythonFunction'; import { PythonModule } from './PythonModule'; -import { PythonPackage } from './PythonPackage'; -import { PythonParameter } from './PythonParameter'; - -test('path without parent', () => { - const pythonModule = new PythonModule('module', 'module'); - expect(pythonModule.path()).toEqual(['module']); -}); - -test('path with parent', () => { - const pythonModule = new PythonModule('module', 'module'); - - // eslint-disable-next-line no-new - new PythonPackage('distribution', 'package', '0.0.1', [pythonModule]); - - expect(pythonModule.path()).toEqual(['package', 'module']); -}); - -test('getByRelativePath with correct path', () => { - const pythonParameter = new PythonParameter('param', 'param', 'param'); - const pythonModule = new PythonModule( - 'module', - 'module', - [], - [], - [], - [new PythonFunction('function', 'function', 'function', [], [pythonParameter])], - ); - expect(pythonModule.getByRelativePath(['function', 'param'])).toBe(pythonParameter); -}); - -test('getByRelativePath with misleading path', () => { - const pythonModule = new PythonModule('module', 'module'); - // eslint-disable-next-line testing-library/prefer-presence-queries - expect(pythonModule.getByRelativePath(['child'])).toBeNull(); -}); test('toString', () => { const pythonModule = new PythonModule('module', 'module'); diff --git a/api-editor/gui/src/features/packageData/model/PythonModule.ts b/api-editor/gui/src/features/packageData/model/PythonModule.ts index 611b68f3f..a09899a28 100644 --- a/api-editor/gui/src/features/packageData/model/PythonModule.ts +++ b/api-editor/gui/src/features/packageData/model/PythonModule.ts @@ -6,7 +6,18 @@ import { PythonFunction } from './PythonFunction'; import { PythonImport } from './PythonImport'; import { PythonPackage } from './PythonPackage'; +interface PythonPackageShallowCopy { + id?: string; + name?: string; + imports?: PythonImport[]; + fromImports?: PythonFromImport[]; + classes?: PythonClass[]; + functions?: PythonFunction[]; +} + export class PythonModule extends PythonDeclaration { + readonly isPublic: boolean; + containingPackage: Optional; constructor( @@ -19,6 +30,8 @@ export class PythonModule extends PythonDeclaration { ) { super(); + this.isPublic = !this.name.split('.').some((it) => it.startsWith('_')); + this.containingPackage = null; this.classes.forEach((it) => { @@ -30,10 +43,6 @@ export class PythonModule extends PythonDeclaration { }); } - isPublicDeclaration(): boolean { - return !this.name.split('.').some((it) => it.startsWith('_')); - } - parent(): Optional { return this.containingPackage; } @@ -42,6 +51,17 @@ export class PythonModule extends PythonDeclaration { return [...this.classes, ...this.functions]; } + shallowCopy({ + id = this.id, + name = this.name, + imports = this.imports, + fromImports = this.fromImports, + classes = this.classes, + functions = this.functions, + }: PythonPackageShallowCopy = {}): PythonModule { + return new PythonModule(id, name, imports, fromImports, classes, functions); + } + toString(): string { return `Module "${this.name}"`; } diff --git a/api-editor/gui/src/features/packageData/model/PythonPackage.test.ts b/api-editor/gui/src/features/packageData/model/PythonPackage.test.ts index 7aab65d29..7c1b383c9 100644 --- a/api-editor/gui/src/features/packageData/model/PythonPackage.test.ts +++ b/api-editor/gui/src/features/packageData/model/PythonPackage.test.ts @@ -1,26 +1,5 @@ -import { PythonClass } from './PythonClass'; -import { PythonModule } from './PythonModule'; import { PythonPackage } from './PythonPackage'; -test('path', () => { - const pythonPackage = new PythonPackage('distribution', 'package', '0.0.1'); - expect(pythonPackage.path()).toEqual(['package']); -}); - -test('getByRelativePath with correct path', () => { - const pythonClass = new PythonClass('Class', 'Class', 'Class'); - const pythonPackage = new PythonPackage('distribution', 'package', '0.0.1', [ - new PythonModule('module', 'module', [], [], [pythonClass]), - ]); - expect(pythonPackage.getByRelativePath(['module', 'Class'])).toBe(pythonClass); -}); - -test('getByRelativePath with misleading path', () => { - const pythonPackage = new PythonPackage('distribution', 'package', '0.0.1'); - // eslint-disable-next-line testing-library/prefer-presence-queries - expect(pythonPackage.getByRelativePath(['child'])).toBeNull(); -}); - test('toString', () => { const pythonPackage = new PythonPackage('distribution', 'package', '0.0.1'); expect(pythonPackage.toString()).toBe(`Package "distribution/package v0.0.1"`); diff --git a/api-editor/gui/src/features/packageData/model/PythonPackage.ts b/api-editor/gui/src/features/packageData/model/PythonPackage.ts index efd8f57d2..81783584e 100644 --- a/api-editor/gui/src/features/packageData/model/PythonPackage.ts +++ b/api-editor/gui/src/features/packageData/model/PythonPackage.ts @@ -1,15 +1,28 @@ import { PythonDeclaration } from './PythonDeclaration'; import { PythonModule } from './PythonModule'; +import { Optional } from '../../../common/util/types'; + +interface PythonPackageShallowCopy { + distribution?: string; + name?: string; + version?: string; + modules?: PythonModule[]; +} export class PythonPackage extends PythonDeclaration { + readonly id: string; + readonly isPublic: boolean = true; + constructor( readonly distribution: string, readonly name: string, readonly version: string, readonly modules: PythonModule[] = [], + private readonly idToDeclaration: Map = new Map(), ) { super(); + this.id = name; this.modules.forEach((it) => { it.containingPackage = this; }); @@ -23,6 +36,21 @@ export class PythonPackage extends PythonDeclaration { return this.modules; } + getDeclarationById(id: string): Optional { + if (this.idToDeclaration.has(id)) { + return this.idToDeclaration.get(id); + } + } + + shallowCopy({ + distribution = this.distribution, + name = this.name, + version = this.version, + modules = this.modules, + }: PythonPackageShallowCopy = {}): PythonPackage { + return new PythonPackage(distribution, name, version, modules, this.idToDeclaration); + } + toString(): string { return `Package "${this.distribution}/${this.name} v${this.version}"`; } diff --git a/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts b/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts index cb016be94..2da973751 100644 --- a/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts +++ b/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts @@ -7,6 +7,7 @@ import { PythonModule } from './PythonModule'; import { PythonPackage } from './PythonPackage'; import { PythonParameter, PythonParameterAssignment } from './PythonParameter'; import { PythonResult } from './PythonResult'; +import { PythonDeclaration } from './PythonDeclaration'; export interface PythonPackageJson { distribution: string; @@ -18,12 +19,16 @@ export interface PythonPackageJson { } export const parsePythonPackageJson = function (packageJson: PythonPackageJson): PythonPackage { + const idToDeclaration = new Map(); + // Functions - const functions = new Map(packageJson.functions.map(parsePythonFunctionJson).map((it) => [it.id, it])); + const functions = new Map( + packageJson.functions.map((it) => parsePythonFunctionJson(it, idToDeclaration)).map((it) => [it.id, it]), + ); // Classes const classes = new Map( - packageJson.classes.map((it) => parsePythonClassJson(it, functions)).map((it) => [it.id, it]), + packageJson.classes.map((it) => parsePythonClassJson(it, functions, idToDeclaration)).map((it) => [it.id, it]), ); return new PythonPackage( @@ -31,8 +36,9 @@ export const parsePythonPackageJson = function (packageJson: PythonPackageJson): packageJson.package, packageJson.version, packageJson.modules - .map((it) => parsePythonModuleJson(it, classes, functions)) + .map((it) => parsePythonModuleJson(it, classes, functions, idToDeclaration)) .sort((a, b) => a.name.localeCompare(b.name)), + idToDeclaration, ); }; @@ -49,8 +55,27 @@ const parsePythonModuleJson = function ( moduleJson: PythonModuleJson, classes: Map, functions: Map, + idToDeclaration: Map, ): PythonModule { - return new PythonModule( + // Classes + const classesInModule = moduleJson.classes + .filter((classId) => classes.has(classId) && classes.get(classId)!.reexportedBy.length === 0) + .map((classId) => classes.get(classId)!); + const reexportedClasses = [...classes.values()].filter( + (it) => it.reexportedBy.length > 0 && it.reexportedBy[0] === moduleJson.id, + ); + const allClasses = [...classesInModule, ...reexportedClasses]; + + // Functions + const functionsInModule = moduleJson.functions + .filter((functionId) => functions.has(functionId) && functions.get(functionId)!.reexportedBy.length === 0) + .map((functionId) => functions.get(functionId)!); + const reexportedFunctions = [...functions.values()].filter( + (it) => it.reexportedBy.length > 0 && it.reexportedBy[0] === moduleJson.id, + ); + const allFunctions = [...functionsInModule, ...reexportedFunctions]; + + const result = new PythonModule( moduleJson.id, moduleJson.name, moduleJson.imports.map(parsePythonImportJson).sort((a, b) => a.module.localeCompare(b.module)), @@ -62,15 +87,11 @@ const parsePythonModuleJson = function ( return moduleComparison; } }), - moduleJson.classes - .sort((a, b) => a.localeCompare(b)) - .filter((classQualifiedName) => classes.has(classQualifiedName)) - .map((classQualifiedName) => classes.get(classQualifiedName) as PythonClass), - moduleJson.functions - .sort((a, b) => a.localeCompare(b)) - .filter((functionUniqueQualifiedName) => functions.has(functionUniqueQualifiedName)) - .map((functionUniqueQualifiedName) => functions.get(functionUniqueQualifiedName) as PythonFunction), + allClasses.sort((a, b) => a.name.localeCompare(b.name)), + allFunctions.sort((a, b) => a.name.localeCompare(b.name)), ); + idToDeclaration.set(moduleJson.id, result); + return result; }; interface PythonImportJson { @@ -100,6 +121,7 @@ interface PythonClassJson { superclasses: string[]; methods: string[]; is_public: boolean; + reexported_by: string[]; description: Optional; docstring: Optional; } @@ -107,21 +129,27 @@ interface PythonClassJson { const parsePythonClassJson = function ( classJson: PythonClassJson, functions: Map, + idToDeclaration: Map, ): PythonClass { - return new PythonClass( + const methods = classJson.methods + .sort((a, b) => a.localeCompare(b)) + .filter((functionId) => functions.has(functionId)) + .map((functionId) => functions.get(functionId) as PythonFunction); + + const result = new PythonClass( classJson.id, classJson.name, classJson.qname, classJson.decorators, classJson.superclasses, - classJson.methods - .sort((a, b) => a.localeCompare(b)) - .filter((functionUniqueQualifiedName) => functions.has(functionUniqueQualifiedName)) - .map((functionUniqueQualifiedName) => functions.get(functionUniqueQualifiedName) as PythonFunction), + methods, classJson.is_public, + classJson.reexported_by, classJson.description ?? '', classJson.docstring ?? '', ); + idToDeclaration.set(classJson.id, result); + return result; }; interface PythonFunctionJson { @@ -132,22 +160,29 @@ interface PythonFunctionJson { parameters: PythonParameterJson[]; results: PythonResultJson[]; is_public: boolean; + reexported_by: string[]; description: Optional; docstring: Optional; } -const parsePythonFunctionJson = function (functionJson: PythonFunctionJson): PythonFunction { - return new PythonFunction( +const parsePythonFunctionJson = function ( + functionJson: PythonFunctionJson, + idToDeclaration: Map, +): PythonFunction { + const result = new PythonFunction( functionJson.id, functionJson.name, functionJson.qname, functionJson.decorators, - functionJson.parameters.map(parsePythonParameterJson), + functionJson.parameters.map((it) => parsePythonParameterJson(it, idToDeclaration)), functionJson.results.map(parsePythonResultJson), functionJson.is_public, + functionJson.reexported_by, functionJson.description ?? '', functionJson.docstring ?? '', ); + idToDeclaration.set(functionJson.id, result); + return result; }; interface PythonParameterJson { @@ -164,8 +199,11 @@ interface PythonParameterJson { type: object; // TODO parse type } -const parsePythonParameterJson = function (parameterJson: PythonParameterJson): PythonParameter { - return new PythonParameter( +const parsePythonParameterJson = function ( + parameterJson: PythonParameterJson, + idToDeclaration: Map, +): PythonParameter { + const result = new PythonParameter( parameterJson.id, parameterJson.name, parameterJson.qname, @@ -176,6 +214,8 @@ const parsePythonParameterJson = function (parameterJson: PythonParameterJson): parameterJson.docstring.description ?? '', parameterJson.type, ); + idToDeclaration.set(parameterJson.id, result); + return result; }; const parsePythonParameterAssignment = function ( diff --git a/api-editor/gui/src/features/packageData/model/PythonParameter.test.ts b/api-editor/gui/src/features/packageData/model/PythonParameter.test.ts index f600341a5..9898ef4a7 100644 --- a/api-editor/gui/src/features/packageData/model/PythonParameter.test.ts +++ b/api-editor/gui/src/features/packageData/model/PythonParameter.test.ts @@ -1,46 +1,5 @@ -import { PythonClass } from './PythonClass'; -import { PythonFunction } from './PythonFunction'; -import { PythonModule } from './PythonModule'; -import { PythonPackage } from './PythonPackage'; import { PythonParameter } from './PythonParameter'; -test('path without parent', () => { - const pythonParameter = new PythonParameter('param', 'param', 'param'); - expect(pythonParameter.path()).toEqual(['param']); -}); - -test('path with ancestors', () => { - const pythonParameter = new PythonParameter('param', 'param', 'param'); - - // eslint-disable-next-line no-new - new PythonPackage('distribution', 'package', '0.0.1', [ - new PythonModule( - 'module', - 'module', - [], - [], - [ - new PythonClass( - 'Class', - 'Class', - 'Class', - [], - [], - [new PythonFunction('function', 'function', 'function', [], [pythonParameter])], - ), - ], - ), - ]); - - expect(pythonParameter.path()).toEqual(['package', 'module', 'Class', 'function', 'param']); -}); - -test('getByRelativePath', () => { - const pythonParameter = new PythonParameter('param', 'param', 'param'); - // eslint-disable-next-line testing-library/prefer-presence-queries - expect(pythonParameter.getByRelativePath(['child'])).toBeNull(); -}); - test('toString', () => { const pythonParameter = new PythonParameter('param', 'param', 'param'); expect(pythonParameter.toString()).toBe(`Parameter "param"`); diff --git a/api-editor/gui/src/features/packageData/model/PythonParameter.ts b/api-editor/gui/src/features/packageData/model/PythonParameter.ts index 7779c8c9b..bbfa6f83f 100644 --- a/api-editor/gui/src/features/packageData/model/PythonParameter.ts +++ b/api-editor/gui/src/features/packageData/model/PythonParameter.ts @@ -40,10 +40,6 @@ export class PythonParameter extends PythonDeclaration { return []; } - isPublicDeclaration(): boolean { - return this.isPublic; - } - isExplicitParameter(): boolean { return this.assignedBy !== PythonParameterAssignment.IMPLICIT; } diff --git a/api-editor/gui/src/features/packageData/model/PythonResult.test.ts b/api-editor/gui/src/features/packageData/model/PythonResult.test.ts index 42984e131..f225a0a54 100644 --- a/api-editor/gui/src/features/packageData/model/PythonResult.test.ts +++ b/api-editor/gui/src/features/packageData/model/PythonResult.test.ts @@ -1,46 +1,5 @@ -import { PythonClass } from './PythonClass'; -import { PythonFunction } from './PythonFunction'; -import { PythonModule } from './PythonModule'; -import { PythonPackage } from './PythonPackage'; import { PythonResult } from './PythonResult'; -test('path without parent', () => { - const pythonResult = new PythonResult('result'); - expect(pythonResult.path()).toEqual(['result']); -}); - -test('path with ancestors', () => { - const pythonResult = new PythonResult('result'); - - // eslint-disable-next-line no-new - new PythonPackage('distribution', 'package', '0.0.1', [ - new PythonModule( - 'module', - 'module', - [], - [], - [ - new PythonClass( - 'Class', - 'Class', - 'Class', - [], - [], - [new PythonFunction('function', 'function', 'function', [], [], [pythonResult])], - ), - ], - ), - ]); - - expect(pythonResult.path()).toEqual(['package', 'module', 'Class', 'function', 'result']); -}); - -test('getByRelativePath', () => { - const pythonResult = new PythonResult('result'); - // eslint-disable-next-line testing-library/prefer-presence-queries - expect(pythonResult.getByRelativePath(['child'])).toBeNull(); -}); - test('toString', () => { const pythonResult = new PythonResult('result'); expect(pythonResult.toString()).toBe(`Result "result"`); diff --git a/api-editor/gui/src/features/packageData/model/PythonResult.ts b/api-editor/gui/src/features/packageData/model/PythonResult.ts index a8f9d3057..9c1ba9e81 100644 --- a/api-editor/gui/src/features/packageData/model/PythonResult.ts +++ b/api-editor/gui/src/features/packageData/model/PythonResult.ts @@ -3,11 +3,17 @@ import { PythonDeclaration } from './PythonDeclaration'; import { PythonFunction } from './PythonFunction'; export class PythonResult extends PythonDeclaration { + readonly id: string; + readonly isPublic: boolean; + containingFunction: Optional; constructor(readonly name: string, readonly typeInDocs: string = '', readonly description: string = '') { super(); + this.id = name; + this.isPublic = true; + this.containingFunction = null; } diff --git a/api-editor/gui/src/features/packageData/model/filters/AbstractPythonFilter.ts b/api-editor/gui/src/features/packageData/model/filters/AbstractPythonFilter.ts index 2237c1f22..5a7ff3e07 100644 --- a/api-editor/gui/src/features/packageData/model/filters/AbstractPythonFilter.ts +++ b/api-editor/gui/src/features/packageData/model/filters/AbstractPythonFilter.ts @@ -78,12 +78,9 @@ export abstract class AbstractPythonFilter { .filter((it) => it !== null); // Create filtered package - return new PythonPackage( - pythonPackage.distribution, - pythonPackage.name, - pythonPackage.version, - modules as PythonModule[], - ); + return pythonPackage.shallowCopy({ + modules: modules as PythonModule[], + }); } /** @@ -116,14 +113,10 @@ export abstract class AbstractPythonFilter { } // Otherwise, create filtered module - return new PythonModule( - pythonModule.id, - pythonModule.name, - pythonModule.imports, - pythonModule.fromImports, - classes as PythonClass[], - functions as PythonFunction[], - ); + return pythonModule.shallowCopy({ + classes: classes as PythonClass[], + functions: functions as PythonFunction[], + }); } /** @@ -151,17 +144,9 @@ export abstract class AbstractPythonFilter { } // Otherwise, create filtered class - return new PythonClass( - pythonClass.id, - pythonClass.name, - pythonClass.qualifiedName, - pythonClass.decorators, - pythonClass.superclasses, - methods as PythonFunction[], - pythonClass.isPublic, - pythonClass.description, - pythonClass.fullDocstring, - ); + return pythonClass.shallowCopy({ + methods: methods as PythonFunction[], + }); } /** @@ -187,16 +172,8 @@ export abstract class AbstractPythonFilter { } // Otherwise, create filtered function - return new PythonFunction( - pythonFunction.id, - pythonFunction.name, - pythonFunction.qualifiedName, - pythonFunction.decorators, + return pythonFunction.shallowCopy({ parameters, - pythonFunction.results, - pythonFunction.isPublic, - pythonFunction.description, - pythonFunction.fullDocstring, - ); + }); } } diff --git a/api-editor/gui/src/features/packageData/model/filters/AnnotationFilter.ts b/api-editor/gui/src/features/packageData/model/filters/AnnotationFilter.ts index a7fb5c3c7..8e33b9b14 100644 --- a/api-editor/gui/src/features/packageData/model/filters/AnnotationFilter.ts +++ b/api-editor/gui/src/features/packageData/model/filters/AnnotationFilter.ts @@ -44,7 +44,7 @@ export class AnnotationFilter extends AbstractPythonFilter { annotations: AnnotationStore, _usages: UsageCountStore, ): boolean { - const id = pythonDeclaration.pathAsString(); + const id = pythonDeclaration.id; switch (this.type) { case AnnotationType.Any: diff --git a/api-editor/gui/src/features/packageData/model/filters/VisibilityFilter.ts b/api-editor/gui/src/features/packageData/model/filters/VisibilityFilter.ts index be4bae544..9a509ca7b 100644 --- a/api-editor/gui/src/features/packageData/model/filters/VisibilityFilter.ts +++ b/api-editor/gui/src/features/packageData/model/filters/VisibilityFilter.ts @@ -43,7 +43,7 @@ export class VisibilityFilter extends AbstractPythonFilter { _annotations: AnnotationStore, _usages: UsageCountStore, ): boolean { - return pythonDeclaration.isPublicDeclaration() === (this.visibility === Visibility.Public); + return pythonDeclaration.isPublic === (this.visibility === Visibility.Public); } } diff --git a/api-editor/gui/src/features/packageData/selectionView/ActionBar.tsx b/api-editor/gui/src/features/packageData/selectionView/ActionBar.tsx index f71b92e44..596779c66 100644 --- a/api-editor/gui/src/features/packageData/selectionView/ActionBar.tsx +++ b/api-editor/gui/src/features/packageData/selectionView/ActionBar.tsx @@ -7,20 +7,28 @@ import { AnnotationStore, selectAnnotations } from '../../annotations/annotation import { useNavigate } from 'react-router'; import { useAppDispatch, useAppSelector } from '../../../app/hooks'; import { UsageCountStore } from '../../usages/model/UsageCountStore'; -import { setAllCollapsedInTreeView, setAllExpandedInTreeView, setExactlyExpandedInTreeView } from '../../ui/uiSlice'; +import { + selectFilter, + setAllCollapsedInTreeView, + setAllExpandedInTreeView, + setExactlyExpandedInTreeView, +} from '../../ui/uiSlice'; +import { selectFilteredPythonPackage, selectFlatSortedDeclarationList } from '../apiSlice'; +import { selectUsages } from '../../usages/usageSlice'; interface ActionBarProps { declaration: PythonDeclaration; - pythonPackage: PythonPackage; - pythonFilter: AbstractPythonFilter; - usages: UsageCountStore; } -export const ActionBar: React.FC = function ({ declaration, pythonPackage, pythonFilter, usages }) { +export const ActionBar: React.FC = function ({ declaration }) { const dispatch = useAppDispatch(); const navigate = useNavigate(); + const allDeclarations = useAppSelector(selectFlatSortedDeclarationList); + const pythonPackage = useAppSelector(selectFilteredPythonPackage); + const pythonFilter = useAppSelector(selectFilter); const annotations = useAppSelector(selectAnnotations); + const usages = useAppSelector(selectUsages); const isMatched = (node: PythonDeclaration): boolean => pythonFilter.shouldKeepDeclaration(node, annotations, usages); @@ -28,7 +36,13 @@ export const ActionBar: React.FC = function ({ declaration, pyth