Skip to content

Commit

Permalink
Merge pull request #2373 from obsidian-tasks-group/add-search-info-class
Browse files Browse the repository at this point in the history
refactor: Introduce (empty) SearchInfo class
  • Loading branch information
claremacrae authored Oct 27, 2023
2 parents 067f50d + 53a0765 commit a326092
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 12 deletions.
13 changes: 9 additions & 4 deletions src/Query/Filter/BooleanField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { PostfixExpression } from 'boon-js';
import { parseFilter } from '../FilterParser';
import type { Task } from '../../Task';
import { Explanation } from '../Explain/Explanation';
import type { SearchInfo } from '../SearchInfo';
import { Field } from './Field';
import { FilterOrErrorMessage } from './FilterOrErrorMessage';
import { Filter } from './Filter';
Expand Down Expand Up @@ -94,8 +95,8 @@ export class BooleanField extends Field {
}
}
// Return the filter with filter function that can run the complete query
const filterFunction = (task: Task) => {
return this.filterTaskWithParsedQuery(task, postfixExpression);
const filterFunction = (task: Task, searchInfo: SearchInfo) => {
return this.filterTaskWithParsedQuery(task, postfixExpression, searchInfo);
};
const explanation = this.constructExplanation(postfixExpression);
return FilterOrErrorMessage.fromFilter(new Filter(line, filterFunction, explanation));
Expand All @@ -122,7 +123,11 @@ export class BooleanField extends Field {
* See here how it works: http://www.btechsmartclass.com/data_structures/postfix-evaluation.html
* Another reference: https://www.tutorialspoint.com/Evaluate-Postfix-Expression
*/
private filterTaskWithParsedQuery(task: Task, postfixExpression: PostfixExpression): boolean {
private filterTaskWithParsedQuery(
task: Task,
postfixExpression: PostfixExpression,
searchInfo: SearchInfo,
): boolean {
const toBool = (s: string | undefined) => {
return s === 'true';
};
Expand All @@ -137,7 +142,7 @@ export class BooleanField extends Field {
// task for each identifier that we find in the postfix expression.
if (token.value == null) throw Error('null token value'); // This should not happen
const filter = this.subFields[token.value.trim()];
const result = filter.filterFunction(task);
const result = filter.filterFunction(task, searchInfo);
booleanStack.push(toString(result));
} else if (token.name === 'OPERATOR') {
// To evaluate an operator we need to pop the required number of items from the boolean stack,
Expand Down
6 changes: 5 additions & 1 deletion src/Query/Filter/Filter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import type { Task } from '../../Task';
import type { Explanation } from '../Explain/Explanation';
import type { SearchInfo } from '../SearchInfo';

/**
* A filtering function, that takes a Task object and returns
* whether it matches a particular filtering instruction.
*
* SearchInfo is being introduced as a Parameter Object, in order to later allow
* more data to be passed from the Query down in to the individual filters.
*/
export type FilterFunction = (task: Task) => boolean;
export type FilterFunction = (task: Task, searchInfo: SearchInfo) => boolean;

/**
* A class that represents a parsed filtering instruction from a tasks code block.
Expand Down
9 changes: 9 additions & 0 deletions src/Query/SearchInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* SearchInfo will soon contain selected data passed in from the {@link Query} being executed.
*
* This is the Parameter Object pattern: it is a container for information that will
* be passed down through multiple levels of code, in order to be able to in future
* pass through more data, without having to update the function signatures of all
* the layers in between.
*/
export class SearchInfo {}
22 changes: 22 additions & 0 deletions tests/CustomMatchers/CustomMatchersForFilters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { SearchInfo } from '../../src/Query/SearchInfo';
import type { Task } from '../../src/Task';
import { TaskBuilder } from '../TestingTools/TaskBuilder';
import { FilterOrErrorMessage } from '../../src/Query/Filter/FilterOrErrorMessage';
import { Filter } from '../../src/Query/Filter/Filter';
import { Explanation } from '../../src/Query/Explain/Explanation';

describe('CustomMatchersForFilters', () => {
it('should check filter with supplied SearchInfo', () => {
// Arrange
const initialSearchInfo = new SearchInfo();
const checkSearchInfoPassedThrough = (_task: Task, searchInfo: SearchInfo) => {
return Object.is(initialSearchInfo, searchInfo);
};
const filter = FilterOrErrorMessage.fromFilter(
new Filter('stuff', checkSearchInfoPassedThrough, new Explanation('explanation of stuff')),
);

// Act, Assert
expect(filter).toMatchTaskWithSearchInfo(new TaskBuilder().build(), initialSearchInfo);
});
});
18 changes: 16 additions & 2 deletions tests/CustomMatchers/CustomMatchersForFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { fromLine } from '../TestHelpers';
import { TaskBuilder } from '../TestingTools/TaskBuilder';
import type { StatusConfiguration } from '../../src/StatusConfiguration';
import { Status } from '../../src/Status';
import { SearchInfo } from '../../src/Query/SearchInfo';

/**
@summary
Expand Down Expand Up @@ -65,6 +66,7 @@ declare global {
interface Matchers<R> {
toBeValid(): R;
toHaveExplanation(expectedExplanation: string): R;
toMatchTaskWithSearchInfo(task: Task, searchInfo: SearchInfo): R;
toMatchTask(task: Task): R;
toMatchTaskFromLine(line: string): R;
toMatchTaskWithDescription(description: string): R;
Expand All @@ -76,6 +78,7 @@ declare global {
interface Expect {
toBeValid(): any;
toHaveExplanation(expectedExplanation: string): any;
toMatchTaskWithSearchInfo(task: Task, searchInfo: SearchInfo): any;
toMatchTask(task: Task): any;
toMatchTaskFromLine(line: string): any;
toMatchTaskWithDescription(description: string): any;
Expand All @@ -87,6 +90,7 @@ declare global {
interface InverseAsymmetricMatchers {
toBeValid(): any;
toHaveExplanation(expectedExplanation: string): any;
toMatchTaskWithSearchInfo(task: Task, searchInfo: SearchInfo): any;
toMatchTask(task: Task): any;
toMatchTaskFromLine(line: string): any;
toMatchTaskWithDescription(description: string): any;
Expand Down Expand Up @@ -141,8 +145,14 @@ export function toHaveExplanation(filter: FilterOrErrorMessage, expectedExplanat
};
}

export function toMatchTask(filter: FilterOrErrorMessage, task: Task) {
const matches = filter.filterFunction!(task);
/**
* Use this test matcher for any filters that need access to any data from the search.
* @param filter
* @param task
* @param searchInfo
*/
export function toMatchTaskWithSearchInfo(filter: FilterOrErrorMessage, task: Task, searchInfo: SearchInfo) {
const matches = filter.filterFunction!(task, searchInfo);
if (!matches) {
return {
message: () => `unexpected failure to match
Expand All @@ -160,6 +170,10 @@ with filter: "${filter.instruction}"`,
};
}

export function toMatchTask(filter: FilterOrErrorMessage, task: Task) {
return toMatchTaskWithSearchInfo(filter, task, new SearchInfo());
}

export function toMatchTaskFromLine(filter: FilterOrErrorMessage, line: string) {
const task = fromLine({
line: line,
Expand Down
2 changes: 2 additions & 0 deletions tests/CustomMatchers/jest.custom_matchers.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
toMatchTaskWithDescription,
toMatchTaskWithHeading,
toMatchTaskWithPath,
toMatchTaskWithSearchInfo,
toMatchTaskWithStatus,
} from './CustomMatchersForFilters';
expect.extend({
Expand All @@ -35,6 +36,7 @@ expect.extend({
toMatchTaskWithDescription,
toMatchTaskWithHeading,
toMatchTaskWithPath,
toMatchTaskWithSearchInfo,
toMatchTaskWithStatus,
});

Expand Down
3 changes: 2 additions & 1 deletion tests/DocumentationSamples/DocsSamplesForStatuses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TaskBuilder } from '../TestingTools/TaskBuilder';
import { MarkdownTable, verifyMarkdownForDocs } from '../TestingTools/VerifyMarkdownTable';
import { StatusRegistry } from '../../src/StatusRegistry';
import { verifyWithFileExtension } from '../TestingTools/ApprovalTestHelpers';
import { SearchInfo } from '../../src/Query/SearchInfo';

function getPrintableSymbol(symbol: string) {
const result = symbol !== ' ' ? symbol : 'space';
Expand Down Expand Up @@ -227,7 +228,7 @@ function verifyTransitionsAsMarkdownTable(statuses: Status[]) {
function filterAllStatuses(filter: FilterOrErrorMessage) {
const cells: string[] = [`Matches \`${filter!.instruction}\``];
tasks.forEach((task) => {
const matchedText = filter!.filter?.filterFunction(task) ? 'YES' : 'no';
const matchedText = filter!.filter?.filterFunction(task, new SearchInfo()) ? 'YES' : 'no';
cells.push(matchedText);
});
table.addRow(cells);
Expand Down
3 changes: 2 additions & 1 deletion tests/Query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TaskLocation } from '../src/TaskLocation';
import { fieldCreators } from '../src/Query/FilterParser';
import type { Field } from '../src/Query/Filter/Field';
import type { BooleanField } from '../src/Query/Filter/BooleanField';
import { SearchInfo } from '../src/Query/SearchInfo';
import { createTasksFromMarkdown, fromLine } from './TestHelpers';
import { shouldSupportFiltering } from './TestingTools/FilterTestHelpers';
import type { FilteringCase } from './TestingTools/FilterTestHelpers';
Expand Down Expand Up @@ -204,7 +205,7 @@ describe('Query parsing', () => {
expect(query.filters.length).toEqual(1);
expect(query.filters[0]).toBeDefined();
// If the boolean query and its sub-query are parsed correctly, the expression should always be true
expect(query.filters[0].filterFunction(task)).toBeTruthy();
expect(query.filters[0].filterFunction(task, new SearchInfo())).toBeTruthy();
});
});

Expand Down
3 changes: 2 additions & 1 deletion tests/Query/Filter/FunctionField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Status } from '../../../src/Status';
import { Priority } from '../../../src/Task';
import { toGroupTaskFromBuilder, toGroupTaskWithPath } from '../../CustomMatchers/CustomMatchersForGrouping';
import { TaskBuilder } from '../../TestingTools/TaskBuilder';
import { SearchInfo } from '../../../src/Query/SearchInfo';

window.moment = moment;

Expand Down Expand Up @@ -43,7 +44,7 @@ describe('FunctionField - filtering', () => {
// Assert
expect(filter).toBeValid();
const t = () => {
filter.filterFunction!(new TaskBuilder().build());
filter.filterFunction!(new TaskBuilder().build(), new SearchInfo());
};
expect(t).toThrow(Error);
expect(t).toThrowError('filtering function must return true or false. This returned "undefined".');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { verifyMarkdownForDocs } from '../../TestingTools/VerifyMarkdownTable';
import { expandPlaceholders } from '../../../src/Scripting/ExpandPlaceholders';
import { makeQueryContext } from '../../../src/Scripting/QueryContext';
import { scan } from '../../../src/Query/Scanner';
import { SearchInfo } from '../../../src/Query/SearchInfo';

/** For example, 'task.due' */
type TaskPropertyName = string;
Expand Down Expand Up @@ -91,7 +92,7 @@ export function verifyFunctionFieldFilterSamplesOnTasks(filters: QueryInstructio
const filterFunction = filterOrErrorMessage.filterFunction!;
const matchingTasks: string[] = [];
for (const task of tasks) {
const matches = filterFunction(task);
const matches = filterFunction(task, new SearchInfo());
if (matches) {
matchingTasks.push(task.toFileLineString());
}
Expand Down
3 changes: 2 additions & 1 deletion tests/TestingTools/FilterTestHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FilterOrErrorMessage } from '../../src/Query/Filter/FilterOrErrorM
import { Task } from '../../src/Task';
import { Query } from '../../src/Query/Query';
import { TaskLocation } from '../../src/TaskLocation';
import { SearchInfo } from '../../src/Query/SearchInfo';
import type { TaskBuilder } from './TaskBuilder';

/**
Expand All @@ -27,7 +28,7 @@ export function testFilter(filter: FilterOrErrorMessage, taskBuilder: TaskBuilde
export function testTaskFilter(filter: FilterOrErrorMessage, task: Task, expected: boolean) {
expect(filter.filterFunction).toBeDefined();
expect(filter.error).toBeUndefined();
expect(filter.filterFunction!(task)).toEqual(expected);
expect(filter.filterFunction!(task, new SearchInfo())).toEqual(expected);
}

/**
Expand Down

0 comments on commit a326092

Please sign in to comment.