Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit 940d2f6

Browse files
authored
feat(gui): more ways to filter by name (#683)
* feat(gui): filter name by regex * feat(gui): filter names exactly by string * feat(gui): consistent filter operators * style: apply automatic fixes of linters * fix(gui): failing tests Co-authored-by: lars-reimann <[email protected]>
1 parent 2d700df commit 940d2f6

File tree

6 files changed

+105
-33
lines changed

6 files changed

+105
-33
lines changed

api-editor/gui/src/common/FilterHelpButton.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,20 @@ export const FilterHelpButton = function () {
7272
</ListItem>
7373
<ListItem>
7474
<ChakraText>
75-
<strong>name:xy</strong>
75+
<strong>name:[operator][string]</strong>
7676
</ChakraText>
7777
<ChakraText>
78-
Displays only elements with names that contain the given string xy.
78+
Displays only elements with matching names (case-sensitive). Replace [operator] with{' '}
79+
<em>=</em> to display only elements that match the [string] exactly or with{' '}
80+
<em>~</em> to display only elements that contain the [string] as a substring.
7981
</ChakraText>
8082
</ListItem>
83+
<ListItem>
84+
<ChakraText>
85+
<strong>name:/[regex]/</strong>
86+
</ChakraText>
87+
<ChakraText>Displays only elements with names that match the given [regex].</ChakraText>
88+
</ListItem>
8189
<ListItem>
8290
<ChakraText>
8391
<strong>annotation:any</strong>
@@ -104,8 +112,8 @@ export const FilterHelpButton = function () {
104112
</ChakraText>
105113
<ChakraText>
106114
Displays only elements that are used a certain number of times. Replace [operator]
107-
with one of <em>&lt;, &lt;=, &gt;=, &gt;</em> or omit it to match by equality.
108-
Replace [expected] with the expected number of usages.
115+
with one of <em>&lt;, &lt;=, =, &gt;=, &gt;</em>. Replace [expected] with the
116+
expected number of usages.
109117
</ChakraText>
110118
</ListItem>
111119
<ListItem>
@@ -114,8 +122,8 @@ export const FilterHelpButton = function () {
114122
</ChakraText>
115123
<ChakraText>
116124
Displays only elements that have a certain usefulness. Replace [operator] with one
117-
of <em>&lt;, &lt;=, &gt;=, &gt;</em> or omit it to match by equality. Replace
118-
[expected] with the expected usefulness.
125+
of <em>&lt;, &lt;=, =, &gt;=, &gt;</em>. Replace [expected] with the expected
126+
usefulness.
119127
</ChakraText>
120128
</ListItem>
121129
<ListItem>

api-editor/gui/src/features/packageData/model/filters/AbstractPythonFilter.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PythonParameter } from '../PythonParameter';
55
import { PythonModule } from '../PythonModule';
66
import { PythonClass } from '../PythonClass';
77
import { PythonFunction } from '../PythonFunction';
8-
import { NameFilter } from './NameFilter';
8+
import { NameStringFilter } from './NameStringFilter';
99
import { initialAnnotationStore as annotations } from '../../../annotations/annotationSlice';
1010
import { PythonDeclaration } from '../PythonDeclaration';
1111
import { UsageCountStore } from '../../../usages/model/UsageCountStore';
@@ -90,7 +90,7 @@ beforeEach(() => {
9090

9191
describe('AbstractPythonFilter::applyToPackage', () => {
9292
test('keeps modules for which the filter returns true, and their ancestors.', () => {
93-
const filter = new NameFilter('test_module_1');
93+
const filter = new NameStringFilter('test_module_1', false);
9494
const filteredPackage = filter.applyToPackage(pythonPackage, annotations, new UsageCountStore());
9595

9696
const modules = filteredPackage.modules;
@@ -104,7 +104,7 @@ describe('AbstractPythonFilter::applyToPackage', () => {
104104
});
105105

106106
test('keeps classes for which the filter returns true, their ancestors, and their descendants', () => {
107-
const filter = new NameFilter('test_class_1');
107+
const filter = new NameStringFilter('test_class_1', false);
108108
const filteredPackage = filter.applyToPackage(pythonPackage, annotations, new UsageCountStore());
109109

110110
const modules = filteredPackage.modules;
@@ -121,7 +121,7 @@ describe('AbstractPythonFilter::applyToPackage', () => {
121121
});
122122

123123
test('keeps methods for which the filter returns true, their ancestors, and their descendants', () => {
124-
const filter = new NameFilter('test_method_1');
124+
const filter = new NameStringFilter('test_method_1', false);
125125
const filteredPackage = filter.applyToPackage(pythonPackage, annotations, new UsageCountStore());
126126

127127
const modules = filteredPackage.modules;
@@ -141,7 +141,7 @@ describe('AbstractPythonFilter::applyToPackage', () => {
141141
});
142142

143143
test('keeps global functions for which the filter returns true, their ancestors, and their descendants', () => {
144-
const filter = new NameFilter('test_global_function_1');
144+
const filter = new NameStringFilter('test_global_function_1', false);
145145
const filteredPackage = filter.applyToPackage(pythonPackage, annotations, new UsageCountStore());
146146

147147
const modules = filteredPackage.modules;
@@ -158,7 +158,7 @@ describe('AbstractPythonFilter::applyToPackage', () => {
158158
});
159159

160160
test('keeps parameters for which the filter returns true, their ancestors, and their descendants', () => {
161-
const filter = new NameFilter('test_parameter_1');
161+
const filter = new NameStringFilter('test_parameter_1', false);
162162
const filteredPackage = filter.applyToPackage(pythonPackage, annotations, new UsageCountStore());
163163

164164
const modules = filteredPackage.modules;

api-editor/gui/src/features/packageData/model/filters/NameFilter.ts renamed to api-editor/gui/src/features/packageData/model/filters/NameRegexFilter.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ import { AnnotationStore } from '../../../annotations/annotationSlice';
88
import { UsageCountStore } from '../../../usages/model/UsageCountStore';
99

1010
/**
11-
* Keeps only declarations that have a given string in their name.
11+
* Keeps only declarations that have a name matching the given regex.
1212
*/
13-
export class NameFilter extends AbstractPythonFilter {
13+
export class NameRegexFilter extends AbstractPythonFilter {
14+
readonly regex: RegExp;
15+
1416
/**
15-
* @param substring The string that must be part of the name of the declaration.
17+
* @param regex The regex that must match the name of the declaration.
1618
*/
17-
constructor(readonly substring: string) {
19+
constructor(regex: string) {
1820
super();
21+
22+
this.regex = RegExp(regex, 'u');
1923
}
2024

2125
shouldKeepModule(pythonModule: PythonModule, annotations: AnnotationStore, usages: UsageCountStore): boolean {
@@ -43,6 +47,6 @@ export class NameFilter extends AbstractPythonFilter {
4347
_annotations: AnnotationStore,
4448
_usages: UsageCountStore,
4549
): boolean {
46-
return pythonDeclaration.name.toLowerCase().includes(this.substring.toLowerCase());
50+
return this.regex.test(pythonDeclaration.name);
4751
}
4852
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { PythonClass } from '../PythonClass';
2+
import { PythonFunction } from '../PythonFunction';
3+
import { PythonModule } from '../PythonModule';
4+
import { PythonParameter } from '../PythonParameter';
5+
import { AbstractPythonFilter } from './AbstractPythonFilter';
6+
import { PythonDeclaration } from '../PythonDeclaration';
7+
import { AnnotationStore } from '../../../annotations/annotationSlice';
8+
import { UsageCountStore } from '../../../usages/model/UsageCountStore';
9+
10+
/**
11+
* Keeps only declarations that have a given string in their name.
12+
*/
13+
export class NameStringFilter extends AbstractPythonFilter {
14+
/**
15+
* @param string The string that must be part of the name of the declaration.
16+
* @param matchExactly Whether the name must match the substring exactly.
17+
*/
18+
constructor(readonly string: string, readonly matchExactly: boolean) {
19+
super();
20+
}
21+
22+
shouldKeepModule(pythonModule: PythonModule, annotations: AnnotationStore, usages: UsageCountStore): boolean {
23+
return this.shouldKeepDeclaration(pythonModule, annotations, usages);
24+
}
25+
26+
shouldKeepClass(pythonClass: PythonClass, annotations: AnnotationStore, usages: UsageCountStore): boolean {
27+
return this.shouldKeepDeclaration(pythonClass, annotations, usages);
28+
}
29+
30+
shouldKeepFunction(pythonFunction: PythonFunction, annotations: AnnotationStore, usages: UsageCountStore): boolean {
31+
return this.shouldKeepDeclaration(pythonFunction, annotations, usages);
32+
}
33+
34+
shouldKeepParameter(
35+
pythonParameter: PythonParameter,
36+
annotations: AnnotationStore,
37+
usages: UsageCountStore,
38+
): boolean {
39+
return this.shouldKeepDeclaration(pythonParameter, annotations, usages);
40+
}
41+
42+
shouldKeepDeclaration(
43+
pythonDeclaration: PythonDeclaration,
44+
_annotations: AnnotationStore,
45+
_usages: UsageCountStore,
46+
): boolean {
47+
if (this.matchExactly) {
48+
return pythonDeclaration.name === this.string;
49+
} else {
50+
return pythonDeclaration.name.includes(this.string);
51+
}
52+
}
53+
}

api-editor/gui/src/features/packageData/model/filters/filterFactory.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createFilterFromString, isValidFilterToken } from './filterFactory';
22
import { ConjunctiveFilter } from './ConjunctiveFilter';
33
import { Visibility, VisibilityFilter } from './VisibilityFilter';
44
import { NegatedFilter } from './NegatedFilter';
5-
import { NameFilter } from './NameFilter';
5+
import { NameStringFilter } from './NameStringFilter';
66
import { UsageFilter } from './UsageFilter';
77
import { UsefulnessFilter } from './UsefulnessFilter';
88
import { greaterThan } from './comparisons';
@@ -57,13 +57,13 @@ describe('createFilterFromString', () => {
5757
});
5858

5959
test('handles name filter', () => {
60-
const completeFilter = createFilterFromString('name:foo');
60+
const completeFilter = createFilterFromString('name:=foo');
6161
expect(completeFilter).toBeInstanceOf(ConjunctiveFilter);
6262
expect((completeFilter as ConjunctiveFilter).filters).toHaveLength(1);
6363

6464
const positiveFilter = (completeFilter as ConjunctiveFilter).filters[0];
65-
expect(positiveFilter).toBeInstanceOf(NameFilter);
66-
expect((positiveFilter as NameFilter).substring).toBe('foo');
65+
expect(positiveFilter).toBeInstanceOf(NameStringFilter);
66+
expect((positiveFilter as NameStringFilter).string).toBe('foo');
6767
});
6868

6969
test('handles usages filter', () => {
@@ -93,7 +93,7 @@ describe('isValidFilterToken', () => {
9393
test.each([
9494
['is:public', true],
9595
['!is:public', true],
96-
['name:foo', true],
96+
['name:=foo', true],
9797
['usages:>2', true],
9898
['usefulness:>2', true],
9999
['annotation:@calledAfter', true], // https://github.com/lars-reimann/api-editor/issues/669

api-editor/gui/src/features/packageData/model/filters/filterFactory.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ConjunctiveFilter } from './ConjunctiveFilter';
2-
import { NameFilter } from './NameFilter';
2+
import { NameStringFilter } from './NameStringFilter';
33
import { AbstractPythonFilter } from './AbstractPythonFilter';
44
import { DeclarationType, DeclarationTypeFilter } from './DeclarationTypeFilter';
55
import { Visibility, VisibilityFilter } from './VisibilityFilter';
@@ -12,6 +12,7 @@ import { equals, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual } fr
1212
import { ParameterAssignmentFilter } from './ParameterAssignmentFilter';
1313
import { PythonParameterAssignment } from '../PythonParameter';
1414
import { RequiredOrOptional, RequiredOrOptionalFilter } from './RequiredOrOptionalFilter';
15+
import { NameRegexFilter } from './NameRegexFilter';
1516

1617
/**
1718
* Creates a filter from the given string. This method handles conjunctions, negations, and non-negated tokens.
@@ -124,13 +125,19 @@ const parsePositiveToken = function (token: string): Optional<AbstractPythonFilt
124125
}
125126

126127
// Name
127-
const nameMatch = /^name:(?<name>\w+)$/u.exec(token);
128-
if (nameMatch) {
129-
return new NameFilter(nameMatch?.groups?.name as string);
128+
const nameStringMatch = /^name:(?<comparison>[=~])(?<name>\w+)$/u.exec(token);
129+
if (nameStringMatch) {
130+
const comparisonOperator = nameStringMatch?.groups?.comparison as string;
131+
return new NameStringFilter(nameStringMatch?.groups?.name as string, comparisonOperator === '=');
132+
}
133+
134+
const nameRegexMatch = /^name:\/(?<regex>.*)\/$/u.exec(token);
135+
if (nameRegexMatch) {
136+
return new NameRegexFilter(nameRegexMatch?.groups?.regex as string);
130137
}
131138

132139
// Usages
133-
const usageMatch = /^usages(?<comparison>:(<|<=|>=|>)?)(?<expected>\d+)$/u.exec(token);
140+
const usageMatch = /^usages:(?<comparison>(<|<=|=|>=|>)?)(?<expected>\d+)$/u.exec(token);
134141
if (usageMatch) {
135142
const comparisonOperator = usageMatch?.groups?.comparison as string;
136143
const comparison = comparisonFunction(comparisonOperator);
@@ -144,7 +151,7 @@ const parsePositiveToken = function (token: string): Optional<AbstractPythonFilt
144151
}
145152

146153
// Usefulness
147-
const usefulnessMatch = /^usefulness(?<comparison>:(<|<=|>=|>)?)(?<expected>\d+)$/u.exec(token);
154+
const usefulnessMatch = /^usefulness:(?<comparison>(<|<=|=|>=|>)?)(?<expected>\d+)$/u.exec(token);
148155
if (usefulnessMatch) {
149156
const comparisonOperator = usefulnessMatch?.groups?.comparison as string;
150157
const comparison = comparisonFunction(comparisonOperator);
@@ -160,15 +167,15 @@ const parsePositiveToken = function (token: string): Optional<AbstractPythonFilt
160167

161168
const comparisonFunction = function (comparisonOperator: string): ((a: number, b: number) => boolean) | null {
162169
switch (comparisonOperator) {
163-
case ':<':
170+
case '<':
164171
return lessThan;
165-
case ':<=':
172+
case '<=':
166173
return lessThanOrEqual;
167-
case ':':
174+
case '=':
168175
return equals;
169-
case ':>=':
176+
case '>=':
170177
return greaterThanOrEqual;
171-
case ':>':
178+
case '>':
172179
return greaterThan;
173180
default:
174181
return null;

0 commit comments

Comments
 (0)